Привет, Хабр!  Меня зовут Василий Материкин, я — Android-разработчик в QIWI. В этом посте я расскажу о применении фича-флагов в QIWI Кошельке. В процессе работы над большими приложениями, в которых много фич и над которыми трудится большая команда разработчиков, часто приходится сталкиваться с такой проблемой как конфигурация приложения с помощью фича-флагов. Мы в QIWI столкнулись с этим два года назад, когда в QIWI Кошельке было создано несколько feature-команд. Выяснилось, что разрабатывать новые фичи с помощью стандартных feature-веток не так удобно, потому что когда над одним проектом работает несколько feature-команд, ветки становятся достаточно объёмными. Потом смержить их в мастер становится довольно непростой задачей, появляются постоянные конфликты.  Поэтому мы решили перейти на Trunk-Based Development (TBD). TBD предлагает работать в небольших ветках и, желательно, чтобы они как можно быстрее были влиты основную ветку. Для этого, конечно же, реализацию нового функционала нужно оформлять небольшими пулл-реквестами, чтобы они быстро проходили ревью и были влиты в основную ветку. Это, в свою очередь, создает другую проблему — когда в главной ветке может появиться код, который ещё не готов к релизу, но при этом нам нужно как-то релизить с этим кодом приложение. Мы же релизы выпускаем достаточно часто. И для этого TBD предлагает пользоваться такими подходами, как Branch by Abstraction (BBA) и Feature Flags (FF). BBA позволяет выделить какой-либо функционал в отдельную абстракцию. Для этого создается интерфейс, который описывает контракт для работы с этим функционалом. Сразу же можно создать его текущую реализацию, просто скопировав код, который сейчас в продакшене. Также создается другая реализация (уже с новым функционалом) и с ней уже начинается работа. То есть обычно первый пулл-реквест при работе с фичей — это выделение кода, создается две реализации, эти изменения вливаются в главную ветку и далее продолжаем работать уже над новой реализацией. При этом в проекте (в продакшене) используется всё ещё старая реализация, пока мы не закончим работать над фичей. Например, мы использовали BBA, когда решили реализовать новый дизайн поиска на главной странице в QIWI-кошельке. Помимо того, что фича-флаги позволяют нам переключаться между старым и новым функционалом, они еще дают следующие возможности: A/B-тесты. Есть две реализации фичи, есть механизм переключения между этими реализациями, поэтому можно легко переключаться между ними. Это позволяет поделить пользователей на группы и предоставлять им разные варианты фичи.  Если в новом функционале обнаружилась какая-то проблема, то можно просто отключить его с помощью фича-флага, при этом не нужно релизить хотфикс приложения в маркете.  Так как используются интерфейсы, то нескольким фича-командам проще работать над несколькими фичами параллельно. Для того чтобы начать использовать Feature Flags в нашем приложении, мы сначала решили посмотреть, какие были на тот момент opensource-решения. В основном это были различные плагины для Gradle, которые позволяют на этапе сборки собрать приложение с каким-то набором флагов, которые попадают, например, в класс BuildConfig в виде констант. Потом в коде мы, соответственно, можем ссылаться на эти флаги и выбирать, какой же из вариантов фичи нам использовать. Но у этого подхода есть несколько минусов:  Нельзя конфигурировать фича-флаги в рантайме, то есть необходимо выпустить новый релиз приложения, чтобы поменять фича-флаг. ,  Нет нескольких источников, из которых можно было бы получать и использовать фича-флаги.  Не совсем удобно, когда в коде есть ветвления, где выбирается в зависимости от флага, какой же функционал использовать. Поэтому мы решили создать свою собственную библиотеку, которая позволила бы работать с фича-флагами удобно и реализовывать все наши потребности. Вначале библиотека состояла из одного модуля и находилась непосредственно в репозитории приложения. Для асинхронных операций в библиотеке использовался фреймворк RxJava 2. Мы слегка переписали её с использованием Kotlin Coroutines и разделением на несколько модулей. Библиотека состоит из нескольких модулей: compiler — кодогенерация на основе аннотаций; feature-manager — основной модуль библиотеки; converter-gson, converter-jackson — разные варианты конвертеров на основе популярных библиотек для парсинга фича-флагов из json; datasource-firebase, datasource-agconnect — интеграция с Firebase Remote Config и Huawei Remote Config. datasource-remote — пример реализации источника флагов на основе своего собственного http эндпоинта с конфигом. datasource-debug — позволяет изменять фича-флаги в рантайме с помощью adb (для дебаг сборок). Рассмотрим, как мы используем эту библиотеку. Способом взаимодействия с библиотекой является интерфейс FeatureManager, который решает все задачи по конфигурации фича-флагов, и у него можно запросить какую-либо фичу. У FeatureManager достаточно простой контракт. Мы можем у него получить фичу, просто указав ее тип, либо можем указать также свою собственную фабрику. Это нужно на тот случай, когда фабрике требуются какие-то зависимости, чтобы создать реализацию фичи.  Для того, чтобы объявить фича-флаги, используются data-классы, в которых может быть одно или несколько полей, с помощью которых можно конфигурировать фичу.  Также для того, чтобы создать какую-либо из реализаций, используется FeatureFactory — абстрактный класс, от которого нужно отнаследоваться. С помощью фича-флага, который приходит в метод createFeature, мы можем создать какую-то реализацию фичи.  Чтобы библиотека знала, какие есть фича-флаги и фабрики для них, используются аннотации @FeatureFlag и @Factory. С помощью Annotation-процессора генерируются реестры с флагами и с фабриками. Нам этот подход подошел, потому что удобно получать фичи из единого класса, который управляет их конфигурацией и не возникает конфликтов при добавлении новых фичей, потому что используется кодогенерация (реестры, которые содержат список фичей и список флагов генерируются автоматически). Посмотрим, как это работает под капотом. Точкой входа в библиотеку является интерфейс FeatureManager. Внутри реализации FeatureManager используется интерфейс FeatureCreator, с помощью которого создаются фичи. Чтобы FeatureCreator знал, какие у него сейчас есть актуальные флаги и фабрики, есть классы-реестры для флагов (FeatureFlagRegistry) и фабрик (FeatureFactoryRegistry). Это те самые классы, которые у нас автоматически генерируются с помощью аннотаций.  Репозиторий FeatureRepository позволяет загружать фича-флаги из нескольких источников данных — FeatureFlagDataSource. Например, значения фича-флагов по умолчанию мы кладем в JSON-файл в папке assets. Это нужно на случай, когда у пользователя, допустим, проблемы с сетевым подключением, и невозможно загрузить актуальную конфигурацию с сервера. Также есть источники, которые реализуется на основе сервиса Firebase Remote Config, если мы собираем приложения для Google Play, или сервиса Huawei Remote Config, если загружаем для AppGallery. Еще один источник загружает флаги с нашего сервера QIWI. Загруженные флаги из сетевых источников попадают в локальный кэш. Оттуда при последующем запуске приложения можно их взять, если с сетью что-то случилось и из сетевых источников не удалось загрузить флаги.  FeatureCreator служит для создания объекта фичи. У него также есть возможность запросить создание фичи, просто передав тип, и, в случае необходимости — кастомную фабрику. Имеется возможность обновить в FeatureCreator значения фича-флагов.  Для создания фичи находим сначала фабрику для нее. Потом находим актуальный флаг и с помощью фабрики и флага создаем реализацию фичи. Здесь используется рефлексия и сгенерированные реестры для флагов и фабрик. В случае, если нет актуального флага, создаем фичу с конфигом по умолчанию. Естественно, логируем сообщение об этом, чтобы потом разобраться, что пошло не так и почему фича-флага вдруг не оказалось.  В имплементации интерфейса FeatureRepository происходит загрузка и слияние флагов из нескольких источников в зависимости от приоритета. Есть метод для сохранения флагов в локальный кэш, чтобы использовать его при следующем запуске приложения в случае каких-либо проблем с сетевым подключением.  FeatureFlagDataSource — это интерфейс, у которого есть несколько реализаций для разных источников. В интерфейсе есть поля для уникального ключа данного источника, приоритета флагов из него и типа источника, например, локальный, удаленный, кэш.  Для парсинга фича-флага из json в объект предусмотрен интерфейс FeatureFlagConverter. Для его реализации можно было использовать любую библиотеку для парсинга. У нас в проекте используется Jackson, но есть также пример реализации на основе Gson.  Чтобы распарсить json, используя сгенерированный реестр FeatureFlagRegistry, находим нужный класс и парсим с помощью конвертера содержимое json. В случае, если не удалось распарсить, логируем это исключение. FeatureFlagRegistry позволяет получить мапу, где ключом является имя фича-флага, а значением — класс фича-флага. Есть также метод для получения реверсивной мапы. FeatureFactoryRegistry позволяет получить мапу, где ключ — класс фича-флага, а значение — класс фабрики для создания фичи. Реализации этих интерфейсов генерируются на основе аннотаций с помощью процессора аннотаций и библиотеки KotlinPoet. В настоящий момент используется плагин kapt, но планируем миграцию на Kotlin Symbol Processing после того, как другая библиотека с кодогенерацией, Dagger, начнет его поддерживать. Результаты, которых мы достигли благодаря созданию библиотеки: Удалось достаточно быстро внедрить TBD. Продуктовые команды смогли быстро добавлять фича-флаги и фичи в проект и решить проблему, когда нужно релизить приложение, но при этом какой-то функционал не готов.  Начали проводить A/B-тесты, так как это стало удобно. В основном мы используем Firebase, но в случае необходимости можем легко перейти на другие источники,так как библиотека позволяет их добавлять. Если в новом функционале вдруг находим какой-то неприятный баг, можем оперативно его отключить без необходимости релизить хотфикс для приложения в маркете. Часто бывает так, что для решения какой-то задачи уже есть готовое решение в OpenSource, которое вам подойдёт. Но, бывает, как в нашем случае, что не оказалось библиотеки, которая бы полностью удовлетворяла всем нашим требованиям. Мы решили рискнуть, написали свою и успешно ее используем в продакшене. Поэтому не бойтесь экспериментировать и создавать свои собственные решения для упрощения разработки Если у вас возникли вопросы о нашем опыте внедрения и использования фича-флагов, не стесняйтесь задавать их в комментариях. Спасибо!