1. Обзор
В этой статье мы кратко определим флаги функций и предложим взвешенный и прагматичный подход к их реализации в приложениях Spring Boot. Затем мы перейдем к более сложным итерациям, использующим различные функции Spring Boot.
Мы обсудим различные сценарии, которые могут потребовать пометки функций, и поговорим о возможных решениях. Мы сделаем это, используя пример приложения Bitcoin Miner.
2. Флаги функций
Флаги функций — иногда называемые переключателями функций — представляют собой механизм, который позволяет нам включать или отключать определенные функции нашего приложения без необходимости изменять код или, в идеале, повторно развертывать наше приложение.
В зависимости от динамики, необходимой для данного флага функции, нам может потребоваться настроить их глобально, для каждого экземпляра приложения или более детально — возможно, для каждого пользователя или запроса.
Как и во многих ситуациях в программной инженерии, важно попытаться использовать самый простой подход, который решает проблему, не добавляя ненужной сложности.
Флаги функций — это мощный инструмент, который при разумном использовании может обеспечить надежность и стабильность нашей системы. Однако при неправильном использовании или недостаточном обслуживании они могут быстро стать источником сложности и головной боли.
Есть много сценариев, в которых флаги функций могут пригодиться:
Разработка на основе магистралей и нетривиальные функции
При разработке на основе магистрали, особенно когда мы хотим продолжать частую интеграцию, мы можем оказаться не готовыми к выпуску определенной части функциональности. Флаги функций могут пригодиться, чтобы мы могли продолжать выпуск, не делая наши изменения доступными до их завершения.
Конфигурация для конкретной среды
Нам может потребоваться определенная функциональность для сброса нашей БД для среды тестирования E2E.
В качестве альтернативы нам может потребоваться использовать другую конфигурацию безопасности для непроизводственной среды, отличную от используемой в производственной среде.
Следовательно, мы могли бы воспользоваться флагами функций для переключения правильной настройки в нужной среде.
A/B-тестирование
Выпуск нескольких решений для одной и той же проблемы и измерение воздействия — убедительный метод, который мы могли бы реализовать с помощью флагов функций.
Канарейка выпускает
При развертывании новых функций мы можем решить делать это постепенно, начиная с небольшой группы пользователей и расширяя их внедрение по мере проверки правильности их поведения. Флаги функций позволяют нам достичь этого.
В следующих разделах мы попытаемся предложить практический подход к рассмотрению вышеупомянутых сценариев.
Давайте разберем различные стратегии для пометки функций, начиная с самого простого сценария, а затем перейдем к более детализированной и более сложной настройке.
3. Флаги функций уровня приложения
Если нам нужно решить любой из первых двух вариантов использования, флаги функций уровня приложения — это простой способ заставить все работать.
Простой флаг функции обычно включает свойство и некоторую конфигурацию, основанную на значении этого свойства.
3.1. Флаги функций с использованием профилей Spring
Весной мы можем воспользоваться профилями . Удобно, что профили позволяют нам выборочно настраивать определенные bean-компоненты. С помощью нескольких конструкций вокруг них мы можем быстро создать простое и элегантное решение для флагов функций на уровне приложения.
Давайте представим, что мы создаем систему добычи биткойнов. Наше программное обеспечение уже находится в производстве, и нам поручено создать экспериментальный улучшенный алгоритм майнинга.
В нашем 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
также включал экспериментальный майнер
и 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 и настраивает тот или иной компонент в зависимости от того, установлено ли для свойства значение true
или false
(или вообще опущено).
Результат очень похож на тот, что был в 3.1, но теперь у нас есть пространство имен. Наличие нашего пространства имен позволяет нам создавать осмысленные файлы YAML/properties:
#[...] Some Spring config
features:
miner:
experimental: true
ui:
cards: true
#[...] Other feature flags
Кроме того, эта новая настройка позволяет нам добавлять префикс к нашим флагам функций — в нашем случае с помощью префикса функций
.
Это может показаться небольшой деталью, но по мере роста и сложности нашего приложения эта простая итерация поможет нам держать под контролем наши флаги функций.
Поговорим о других преимуществах этого подхода.
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. Поддержание лагеря в чистоте
Хотя это может показаться очевидным, после того как мы вдумчиво реализовали наши функциональные флаги, не менее важно сохранять дисциплину и избавляться от них, когда они больше не нужны.
Флаги функций для первого варианта использования — разработка на основе магистрали и нетривиальные функции — обычно недолговечны . Это означает, что нам нужно убедиться, что наши ConfigProperties,
наша конфигурация Java и наши файлы YAML
остаются чистыми и актуальными.
4. Более детализированные флаги функций
Иногда мы попадаем в более сложные ситуации. Для A/B-тестирования или канареечных релизов нашего предыдущего подхода просто недостаточно.
Чтобы получить флаги функций на более детальном уровне, нам может потребоваться создать наше решение. Это может включать в себя настройку нашего пользовательского объекта для включения информации о функциях или, возможно, расширение нашей веб-инфраструктуры.
Однако загрязнять наших пользователей флагами функций может не всем понравиться, и есть другие решения.
В качестве альтернативы мы могли бы воспользоваться некоторыми встроенными инструментами , такими как Togglz . Этот инструмент добавляет некоторую сложность, но предлагает хорошее готовое решение и обеспечивает первоклассную интеграцию со Spring Boot .
Togglz поддерживает различные стратегии активации :
- Имя пользователя: флаги, связанные с конкретными пользователями
- Постепенное развертывание: флаги включены для определенного процента пользовательской базы. Это полезно для выпусков Canary, например, когда мы хотим проверить поведение наших функций.
- Дата выпуска: мы можем запланировать включение флагов на определенную дату и время. Это может быть полезно для запуска продукта, скоординированного выпуска или предложений и скидок.
- IP-адрес клиента: помеченные функции на основе IP-адресов клиентов. Это может пригодиться при применении конкретной конфигурации к конкретным клиентам, если у них есть статические IP-адреса.
- IP-адрес сервера: в этом случае IP-адрес сервера используется для определения того, должна ли функция быть включена или нет. Это может быть полезно и для канареечных выпусков, с немного другим подходом, чем постепенное развертывание — например, когда мы хотим оценить влияние на производительность в наших экземплярах.
- ScriptEngine: мы могли бы включить флаги функций на основе произвольных скриптов . Это, пожалуй, самый гибкий вариант
- Системные свойства: мы можем установить определенные системные свойства, чтобы определить состояние флага функции. Это было бы очень похоже на то, чего мы достигли с нашим самым простым подходом.
5. Резюме
В этой статье у нас была возможность поговорить о флагах функций. Кроме того, мы обсудили, как Spring может помочь нам реализовать некоторые из этих функций без добавления новых библиотек.
Мы начали с определения того, как этот шаблон может помочь нам в нескольких распространенных случаях использования.
Затем мы создали несколько простых решений, используя готовые инструменты Spring и Spring Boot. Таким образом, мы придумали простую, но мощную конструкцию для пометки функций.
Ниже мы сравнили несколько альтернатив. Переход от более простого и менее гибкого решения к более сложному, хотя и более сложному шаблону.
Наконец, мы кратко предоставили несколько рекомендаций по созданию более надежных решений. Это полезно, когда нам нужна более высокая степень детализации.