Автор оригинала: José Carlos Valero Sánchez.
1. Обзор
В этой статье мы кратко определим флаги функций и предложим самоуверенный и прагматичный подход к их реализации в приложениях Spring Boot. Затем мы углубимся в более сложные итерации, используя преимущества различных функций Spring Boot.
Мы обсудим различные сценарии, которые могут потребовать маркировки функций, и обсудим возможные решения. Мы сделаем это, используя пример приложения Bitcoin Miner.
2. Флаги функций
Флаги функций – иногда называемые переключателями функций – это механизм, который позволяет нам включать или отключать определенные функции нашего приложения без необходимости изменять код или, в идеале, повторно развертывать наше приложение.
В зависимости от динамики, требуемой для данного флага функции, нам может потребоваться настроить их глобально, для каждого экземпляра приложения или более детально – возможно, для каждого пользователя или запроса.
Как и во многих ситуациях в разработке программного обеспечения, важно попытаться использовать наиболее простой подход, который решает проблему без добавления ненужной сложности.
Флаги функций-это мощный инструмент, который при разумном использовании может обеспечить надежность и стабильность нашей системы. Однако, когда они используются неправильно или недостаточно обслуживаются, они могут быстро стать источником сложности и головной боли.
Существует множество сценариев, в которых могут пригодиться флаги функций:
Разработка на основе магистрали и нетривиальные функции
При разработке на основе магистрали, особенно когда мы хотим продолжать частую интеграцию, мы можем оказаться не готовыми к выпуску определенной части функциональности. Флаги функций могут пригодиться, чтобы мы могли продолжать выпускать, не делая наши изменения доступными до завершения.
Конфигурация для конкретной среды
Возможно, нам потребуется определенная функциональность для сброса нашей базы данных для среды тестирования E2E.
В качестве альтернативы нам может потребоваться использовать другую конфигурацию безопасности для непроизводственных сред, отличную от той, которая используется в производственной среде.
Следовательно, мы могли бы воспользоваться флагами функций для переключения правильной настройки в нужной среде.
A/B тестирование
Выпуск нескольких решений для одной и той же проблемы и измерение воздействия-это убедительный метод, который мы могли бы реализовать с помощью флагов функций.
Выпуск канарейки
При развертывании новых функций мы можем решить делать это постепенно, начиная с небольшой группы пользователей и расширяя ее внедрение по мере проверки правильности ее поведения. Флаги функций позволяют нам достичь этого.
В следующих разделах мы попытаемся предложить практический подход к решению вышеупомянутых сценариев.
Давайте разберем различные стратегии для маркировки функций, начиная с самого простого сценария, чтобы затем перейти к более детальной и более сложной настройке.
3. Флаги функций на уровне приложений
Если нам нужно решить любой из первых двух вариантов использования, флаги функций уровня приложения-это простой способ заставить все работать.
Простой флаг функции обычно включает свойство и некоторую конфигурацию, основанную на значении этого свойства.
3.1. Флаги Функций С Использованием Профилей Пружин
Весной мы сможем воспользуйтесь преимуществами профилей . Удобно, что профили позволяют нам выборочно настраивать определенные компоненты. С помощью нескольких конструкций вокруг них мы можем быстро создать простое и элегантное решение для флагов функций на уровне приложений.
Давайте представим, что мы строим систему майнинга биткоинов. Наше программное обеспечение уже находится в производстве, и нам поручено создать экспериментальный, улучшенный алгоритм майнинга.
В нашем JavaConfig мы могли бы профилировать наши компоненты:
@Configuration public class ProfiledMiningConfig { @Bean @Profile("!experimental-miner") public BitcoinMiner defaultMiner() { return new DefaultBitcoinMiner(); } @Bean @Profile("experimental-miner") public BitcoinMiner experimentalMiner() { return new ExperimentalBitcoinMiner(); } }
Затем, с предыдущей конфигурацией, нам просто нужно включить наш профиль, чтобы подписаться на нашу новую функциональность. Существует множество способов настройки нашего приложения в целом и включения профилей в частности . Точно так же существуют утилиты тестирования , чтобы облегчить нашу жизнь.
Пока наша система достаточно проста, мы могли бы затем создать конфигурацию на основе среды, чтобы определить, какие флаги функций применять, а какие игнорировать.
Давайте представим, что у нас есть новый пользовательский интерфейс, основанный на картах вместо таблиц, вместе с предыдущим экспериментальным майнером.
Мы хотели бы включить обе функции в нашей среде принятия (UAT). Мы могли бы создать следующую группу профилей в нашем файле application.yml :
spring: profiles: group: uat: experimental-miner,ui-cards
При наличии предыдущего свойства нам просто нужно включить профиль UAT в среде UAT, чтобы получить желаемый набор функций. Конечно, мы также могли бы добавить файл application-uat.yml в наш проект, чтобы включить дополнительные свойства для настройки нашей среды.
В нашем случае мы хотим, чтобы профиль uat также включал experimental-miner и ui-карты.
Примечание: если мы используем версию Spring Boot до версии 2.4.0, мы бы использовали свойство spring.profiles.include в документе, относящемся к профилю UAT, для настройки дополнительных профилей. По сравнению с spring.profiles.active, первый позволяет нам включать профили аддитивным образом.
3.2. Флаги Объектов С использованием пользовательских Свойств
Профили-это отличный и простой способ выполнить свою работу. Однако нам могут потребоваться профили для других целей. Или, возможно, мы могли бы захотеть создать более структурированную инфраструктуру флагов функций.
Для этих сценариев предпочтительным вариантом могут быть пользовательские свойства.
Давайте перепишем наш предыдущий пример, используя преимущества @ConditionalOnProperty и нашего пространства имен :
@Configuration public class CustomPropsMiningConfig { @Bean @ConditionalOnProperty( name = "features.miner.experimental", matchIfMissing = true) public BitcoinMiner defaultMiner() { return new DefaultBitcoinMiner(); } @Bean @ConditionalOnProperty( name = "features.miner.experimental") public BitcoinMiner experimentalMiner() { return new ExperimentalBitcoinMiner(); } }
Предыдущий пример строится поверх условной конфигурации Spring Boot и настраивает тот или иной компонент в зависимости от того, установлено ли для свойства значение истинный или ложный (или вообще опущено).
Результат очень похож на тот, что был в 3.1, но теперь у нас есть наше пространство имен. Наличие нашего пространства имен позволяет нам создавать значимые файлы YAML/properties:
#[...] Some Spring config features: miner: experimental: true ui: cards: true #[...] Other feature flags
Кроме того, эта новая настройка позволяет нам префиксировать наши флаги функций – в нашем случае, используя префикс features //.
Это может показаться незначительной деталью, но по мере роста нашего приложения и увеличения сложности эта простая итерация поможет нам держать наши флаги функций под контролем.
Давайте поговорим о других преимуществах этого подхода.
3.3. Использование свойств @ConfigurationProperties
Как только мы получим префиксный набор свойств, мы можем создать POJO, украшенный @ConfigurationProperties, чтобы получить программный дескриптор в нашем коде.
Следуя нашему постоянному примеру:
@Component @ConfigurationProperties(prefix = "features") public class ConfigProperties { private MinerProperties miner; private UIProperties ui; // standard getters and setters public static class MinerProperties { private boolean experimental; // standard getters and setters } public static class UIProperties { private boolean cards; // standard getters and setters } }
Объединяя состояние флагов функций в единое целое, мы открываем новые возможности, позволяя нам легко предоставлять эту информацию другим частям нашей системы, таким как пользовательский интерфейс, или нижестоящим системам.
3.4. Раскрытие конфигурации функций
Наша система майнинга биткоинов получила обновление пользовательского интерфейса, которое еще не полностью готово. По этой причине мы решили отметить его. У нас может быть одностраничное приложение, использующее React, Angular или Vue.
Независимо от технологии, нам нужно знать, какие функции включены, чтобы мы могли соответствующим образом отображать нашу страницу.
Давайте создадим простую конечную точку для обслуживания нашей конфигурации, чтобы наш пользовательский интерфейс мог запрашивать серверную часть, когда это необходимо:
@RestController public class FeaturesConfigController { private ConfigProperties properties; // constructor @GetMapping("/feature-flags") public ConfigProperties getProperties() { return properties; } }
Могут существовать более сложные способы предоставления этой информации, такие как создание пользовательских конечных точек привода . Но ради этого руководства конечная точка контроллера кажется достаточно хорошим решением.
3.5. Поддержание чистоты в Лагере
Хотя это может показаться очевидным, как только мы вдумчиво внедрили наши флаги функций, не менее важно сохранять дисциплину в избавлении от них, когда они больше не нужны.
Флаги функций для первого варианта использования – разработка на основе магистрали и нетривиальные функции – обычно недолговечны . Это означает, что нам нужно будет убедиться, что ваши свойства Config, конфигурация Java и файлы YAML остаются чистыми и актуальными.
4. Более Детализированные Флаги Функций
Иногда мы оказываемся в более сложных сценариях. Для A/B-тестирования или выпусков canary нашего предыдущего подхода просто недостаточно.
Чтобы получить флаги функций на более детальном уровне, нам, возможно, потребуется создать наше решение. Это может включать в себя настройку сущности пользователя для включения информации о конкретных функциях или, возможно, расширение нашей веб-платформы.
Однако загрязнение наших пользователей флагами функций может быть не очень привлекательной идеей для всех, и есть другие решения.
В качестве альтернативы мы могли бы воспользоваться некоторыми встроенными инструментами , такими как Toggle . Этот инструмент добавляет некоторую сложность, но предлагает хорошее готовое решение и обеспечивает первоклассную интеграцию с Spring Boot .
Toggle поддерживает различные стратегии активации :
- Имя пользователя: Флаги, связанные с конкретными пользователями
- Постепенное развертывание: Флаги включены для определенного процента пользователей. Это полезно для выпусков Canary, например, когда мы хотим проверить поведение наших функций
- Дата выпуска: Мы могли бы запланировать включение флагов на определенную дату и время. Это может быть полезно для запуска продукта, согласованного выпуска или предложений и скидок
- IP-адрес клиента: Помеченные функции на основе IP-адресов клиентов. Они могут пригодиться при применении конкретной конфигурации к конкретным клиентам, учитывая, что у них есть статические IP-адреса
- IP-адрес сервера: В этом случае IP-адрес сервера используется для определения того, должна ли функция быть включена или нет. Это может быть полезно и для выпусков canary, но с несколько иным подходом, чем постепенное развертывание, например, когда мы хотим оценить влияние производительности в наших экземплярах
- Движок скриптов: Мы могли бы включить флаги функций на основе произвольных скриптов . Это, пожалуй, самый гибкий вариант
- Системные свойства: Мы могли бы задать определенные системные свойства для определения состояния флага объекта. Это было бы очень похоже на то, чего мы достигли с помощью нашего самого простого подхода
5. Резюме
В этой статье у нас была возможность поговорить о флагах функций. Кроме того, мы обсудили, как Spring может помочь нам достичь некоторых из этих функций без добавления новых библиотек.
Мы начали с определения того, как этот шаблон может помочь нам в нескольких распространенных случаях использования.
Затем мы создали несколько простых решений, используя готовые инструменты Spring и Spring Boot. С этим мы придумали простую, но мощную конструкцию для маркировки функций.
Ниже мы сравнили несколько альтернатив. Переход от более простого и менее гибкого решения к более сложному, хотя и более сложному шаблону.
Наконец, мы кратко изложили несколько рекомендаций по созданию более надежных решений. Это полезно, когда нам нужна более высокая степень детализации.