Привет, хабр! Меня зовут Егор, я бэкенд-разработчик в команде ЦФА в Альфа-Банке.
Сейчас мы рассматриваем возможность внедрения фича-тоглов в наш проект и проводим исследование уже существующих решений. В рамках него мне удалось глубоко познакомиться с Unleash — самой популярной платформой для фича-тоглов на данный момент.
В статье пробежимся по основным понятиям и сущностям в Unleash, разберем примеры кода на Java и также с помощью метрик проверим, работает ли это на самом деле.
В конце будет ссылка на демо-проект, в котором можно одной командой поднять все окружение и поэкспериментировать с настройками Unleash.
Почему Unleash
Готовых инструментов для фича-тоглов полно. Это неудивительно, ведь для большинства проектов достаточно, чтобы платформа хотя бы хранила и отдавала список тоглов и их состояние. Отсюда даже возникает соблазн написать свой собственный инструмент, в котором можно учесть специфику проекта. Но особых требований к инструменту у нас нет, да и времени на его создание, скорее всего, бизнес не выделит. Поэтому мы решили искать среди готовых решений.
При выборе платформы мы опирались на следующие критерии:
Наличие open source версии проекта.
Код активно поддерживается, нет критических уязвимостей в зависимостях.
Наличие приличного SDK для Java.
Платформа должна работать как standalone-приложение.
Чем популярнее инструмент, тем лучше.
Ранее у меня уже был опыт работы с фича-тогглами на одном из прошлых проектов. На основе него даже выходила статья «Канареечные релизы на Camunda». Тогда мы использовали Togglz, как платформу для фича-тоглов. Но у него есть существенное ограничение: работает только в виде подключаемой библиотеки в конкретный сервис. На текущем проекте у нас больше 50 сервисов, и подключать Togglz в каждый из них не целесообразно. К тому же бывают такие фичи, которые задевают сразу несколько сервисов.
В итоге мы остановились на Unleash. У него:
Большое комьюнити: 12 000 звезд на GitHub, в среднем 50 коммитов в неделю за последний год.
Есть SDK для всех основных языков программирования: Java, Go, Python, C#, Node.js. Для приложений на Spring есть готовый стартер, сильно упрощающий конфигурацию.
Есть бесплатная Open Source версия, а также готовые Docker-образы и helm-чарты.
Подробнейшая документация: как по самой платформе, так и по её API.
Удобный и продуманный UI-интерфейс.
Взаимодействие с Unleash
На картинке ниже можно увидеть пример взаимодействия Unleash с несколькими микросервисами в системе.

Обычно работа с тоглами проходит так:
Через UI создаем новый тоггл.
В сервисы проекта подключаем Unleash SDK и настраиваем подключение к Unleash. В код добавляем проверки на включение тоглов. Теперь каждые 10 секунд сервисы сами забирают из Unleash свежую конфигурацию тоглов.
В UI настраиваем условия для включения тоггла.
Сервисы периодически подтягивают себе свежую конфигурацию из Unleash и сохраняют в кэш.
Когда пользователь, вызывающий API сервиса, доходит до проверки тогла, Unleash SDK на основе полученной ранее конфигурации проверяет, включён ли тогл конкретно для этого пользователя. Если пользователь прошёл условия отбора, то фича становится ему доступна.
Пример проверки тоггла в Java:
if (unleash.isEnabled("ifelse-feature")) {
// логика при включенном тоггле
} else {
// логика при выключенном тоггле
}
Важно отметить, что при каждом вызове unleash.isEnabled() не происходит обращения по REST в Unleash. Проверка на включение тоггла происходит внутри сервиса на основе заранее подтянутой из Unleash конфигурации.
Тоглы
В терминологии Unleash они называются фичами. Помимо названия и статуса вкл/выкл у них есть несколько дополнительных полей и признаков. Вот некоторые из них:
Active — тоггл готов к использованию или уже используется.
Potentially stale — проставляется автоматически, когда истекает срок жизни тогла.
Stale — этот признак уже явно выставляется человеком в UI. Пометка тогла как Stale подсвечивает команде, что его пора удалять из кода.
Archived — тогл заархивирован и больше не может использоваться сервисами. При необходимости его можно оживить и тогда он снова будет доступен.
Также у тогла можно определить его тип. Он поможет другим коллегам понять назначение фичи:
Release — для релиза новой функциональности. Срок жизни 40 дней.
Experiment — для A/B тестирования, проверок гипотез, дебага и т.п. Срок жизни 40 дней.
Operational — подойдет командам, работающим по TBD. Этим типом помечаются тогглы с небольшими техническими изменениями в коде, например при работе через Branch by Abstraction. Срок жизни 7 дней.
Kill switch — для случаев, когда в проекте есть функциональность, которую нужно периодически отключать и включать обратно. Например, на одном из моих прошлых проектов мы такими тоглами отключали нотификации клиентов на новогодние праздники. Тоглы с этим типом имеют неограниченный срок жизни.
Permission — используется для выдачи ролей и доступов пользователям. Таким образом Unleash можно использовать и как сервис авторизации. Обновление прав происходит почти моментально и не потребует отзыва токена, как в случае с передачей ролей в JWT. Срок жизни у таких тогглов не ограничен.
Тип влияет только на срок жизни тогла, по истечении которого он будет помечен как Potentially stale. Это не приведет к удалению тогла, но он будет помечен как deprecated и занесен во вкладку с техдолгом.
Стратегии
Для каждого тогла можно добавить одну или несколько стратегий. С помощью них можно ограничить процент пользователей, которым будет доступна фича. В Unleash по умолчанию добавлены несколько стратегий:
Gradual rollout — позволяет включить фичу на определенный процент пользователей. Самая популярная стратегия, обычно ее достаточно для большинства случаев.
Standart — работает как выключатель, который включает фичу либо для всех, либо ни для кого. Можно сказать, что эта стратегия избыточна, так как может быть заменена стратегией Gradual rollout, при выставлении значения в 0% или в 100% пользователей.
IP — позволяет включить фичу только для пользователей с IP адресом из белого списка.
Hosts — аналогично прошлой стратегии, но уже для приложений с хостами из белого списка.

В Unleash, как и во многих других платформах управления фича-тоглами, есть возможность создавать свои стратегии. Но разработчики рекомендуют вместо создания стратегий использовать более гибкий механизм ограничений.
(Не)случайное распределение в стратегии Gradual rollout
Допустим мы выбрали стратегию Gradual rollout и выставили значение 20%. Но как понять, попадет ли текущий пользователь в эти 20%?
Расчет всегда происходит на основании контекстного поля пользователя, которое помечено как stickiness. От этой строки берется хэш по алгоритму MurmurHash и берётся остаток от деления на 100. Если полученное число меньше 20, то проверка тогла выдаёт true и фича для пользователя будет включена.
Но если пользователь второй раз запросит фичу, будет ли результат тем же? Да, если значение stickiness поля у пользователя не менялось. Алгоритм хэширования при одинаковых входных данных будет выдавать тот же хэш, значит результат проверки тогла не изменится.
Но при этом всё равно есть риск получить другой результат при повторной проверке, если неудачно выбрать stickiness-поле. Например, если использовать возраст, то при наступлении дня рождения у пользователя алгоритм сгенерирует совершенно другой хэш и проверка тогла может дать другой результат.
Поэтому важно в качестве stickiness-поля выбирать поля, которые не меняются, например id пользователя или его логин. По умолчанию в Unleash используется поле userId, а при его отсутствии sessionId.
Ограничения
Ограничение представляет собой условие, с помощью которого можно отфильтровать часть пользователей, которым будет доступна фича. Это сильно напоминает проверку атрибутов в модели ABAC. При добавлении нескольких ограничений они будут работать по правилу логического И. Выглядит это вот так:

В этом примере фича будет доступна всем клиентам с именем Иван, с возрастом старше 18 лет и с подключенным тарифом ULTIMATE или VIP.
Но добавление ограничений позволяет только сузить круг пользователей. Есть ли возможность также включить фичу для другой группы пользователей, которая будет отбираться по другим правилам?
Да, для этого мы можем добавлять дополнительные стратегии, и они уже будут работать по правилу ИЛИ:

Теперь фича будет доступна также для всех бета-тестеров, которые перечислены в сегменте beta-testers. На сегментах подробнее остановимся в следующем разделе.
На каждую фичу можно добавить до 30 стратегий, в каждой из которых до 30 ограничений.
Сегменты
Сегменты — одна из крутых фишек Unleash, которых нет в других платформах. Они представляют собой набор ограничений, который сохраняется отдельно, без привязки к конкретному тоглу. С помощью сегментов можно переиспользовать ограничения в нескольких тоглах.
Проблема: у компании есть пул бета-тестеров, которые проверяют новую фичу перед релизом на всю аудиторию. Их состав часто меняется, поэтому команде разработки постоянно приходится добавлять новых бета-тестеров сразу в несколько тоглов. И при появлении нового тогла, каждый раз через UI заносить в него список всех бета-тестеров.
Решение: можно создать сегмент со списком бета-тестеров и подключить его в настройках стратегии у тогла. При редактировании сегмента, изменения автоматически подтянутся во все тоглы, которые его используют.
Контекстные поля
Всего в Unleash есть только 5 встроенных полей, по которым мы можем настраивать ограничения:
UserId — уникальный идентификатор пользователя, как правило сюда записывают его id, логин или емейл.
SessionId — id сессии, например JSESSIONID из Tomcat.
AppName — сервис, в котором используется тоггл. Это название мы указываем при настройке подключения сервиса к Unleash.
CurrentTime — текущее время. С помощью него можно включить фичу в строго заданное время, например ровно в полночь.
Environment — окружение, на котором запущен сервис. Например прод, предпрод или тест.
Но что если в проекте есть другие атрибуты у пользователя? Можно ли по ним настраивать ограничения? Да, можно, но для этого нужно сделать две вещи:
№1. Добавить контекстное поле в админке Unleash в разделе Context fields. Помимо имени и описания поля, можно будет задать две дополнительные настройки:
Legal values — здесь вы сможете задать ограниченный набор значений, которое может принимать это поле. Стоит заполнить, если в коде это поле используется как enum. Это сильно упростит настройку ограничений по этому полю.
Custom stickiness — этот признак указывает, что именно это поле будет использоваться при расчете прилипания пользователя к включению тогла. По умолчанию в качестве stickiness поля используется userId. По этому если в приложении userId уже заполняется каким-нибудь уникальным для пользователя значением, то не рекомендую включать эту галочку.
№2. В коде заполнить контекстные поля на основе данных текущего пользователя. Это можно сделать двумя способами:
Использовать UnleashContextProvider и заполнить его всеми данными перед вызовом unleash.isEnabled(). Часто это делают через AoP беря данные входящего HTTP запроса или сообщения от брокера сообщений.
Передать контекст явно, при вызове unleash.isEnabled().
Пример явного заполнения контекста перед проверкой тогла:
UnleashContext context = UnleashContext.builder()
.userId(user)
.addProperty("tariff", tariff)
.addProperty("age", age.toString())
.build();
if (unleash.isEnabled("ifelse-feature", context)) {
// логика при включенном тогле
}
Теперь мы можем использовать наши контекстные поля в настройке ограничений. Если выставим ограничение age > 18, то получим следующую картину:

Тэги
Со временем количество тоглов в Unleash будет постепенно увеличиваться, особенно в больших проектах. Поиск нужного тогла будет отнимать всё больше времени и концентрации разработчиков.
Чтобы избежать такой ситуации, в Unleash добавлены тэги. С их помощью можно фильтровать тоглы на странице поиска. К примеру, можно создать тэг для каждой команды и пометить им все тоглы, за которые она ответственна. На каждый тоггл можно навесить несколько тэгов.
На этом разработчики Unleash не остановились и добавили типы тэгов. Теперь у каждого тэга обязательно указан его тип, поэтому при создании тэга с названием myTag и типом simple, он будет отображаться как simple:myTag.
Тип тэга ни на что не влияет, это просто метаинформация. Смысл он обретает только при подключении в Unleash сторонних плагинов, например, плагин для интеграции со Slack. Если он подключен, то при переключении тогла с тэгом slack:my-team-channel, полетит нотификация в канал my-team-channel.
Варианты
В большинстве платформ для фича тоглов можно описать только два состояния фичи: когда тоггл включен и когда выключен. Ещё одна из интересных особенностей Unleash — это возможность сделать больше двух вариантов реализации фичи.
В первую очередь это будет полезно для A/B-тестирования, когда нужно проверить несколько вариантов реализации фичи на разных группах пользователей. Например, написать несколько алгоритмов рекомендаций и узнать, какой из них приводит к большей активности аудитории.
Для этого необходимо в настройках тогла в разделе Variants добавить несколько вариантов и раздать им веса. Вес — это процент пользователей от общего их количества. По умолчанию, весь трафик будет распределяться равномерно между всеми вариантами.

В коде получение текущего варианта будет выглядеть примерно так:
switch (unleash.getVariant("variants-feature").getName()) {
case "firstVariant": {
// логика для первого варианта
break;
}
case "secondVariant": {
// логика для второго варианта
break;
}
case "thirdVariant": {
// логика для третьего варианта
break;
}
default: {
// логика по умолчанию
break;
}
}
В Spring это можно сделать декларативно, через интерфейс с аннотациями:
public interface VariantTestService {
@Toggle(name = "variants-feature",
variants = @FeatureVariants(
fallbackBean = "firstVariantTestService",
variants = {
@FeatureVariant(name = "firstVariant", variantBean = "firstVariantBean"),
@FeatureVariant(name = "secondVariant", variantBean = "secondVariantBean"),
@FeatureVariant(name = "thirdVariant", variantBean = "thirdVariantBean"),
}))
void doSomething();
}
Ещё с помощью вариантов можно передавать данные из Unleash в сервисы. У каждого варианта можно задать payload в виде строки. Сервис считает его вместе с остальной конфигурацией тоглов. Пример получения payload в коде:
var payload = unleash.getVariant("my-feature")
.getPayload()
.map(Payload::getValue)
.orElse("empty");
Стоит отметить, что создание вариантов в стратегии тогла никак не запрещает добавление других ограничений. Если пользователь не проходит под ограничения тогла, то ему будет доступен только вариант по умолчанию.
Другие возможности
Unleash собирает аудит всех действий пользователей. Его можно посмотреть в разделе Events.
Unleash ведет статистику сколько раз фича была включена или выключена в разрезе по сервисам. Это позволяет визуально определить, правильно ли заданы ограничения для фичи.
Есть минимальная ролевая модель с 3 ролями: Admin, Editor, Viewer.
Интеграции с Slack, Jira, Teams.
Раздел с сервисами подключенными к Unleash. В нем отображается список всех подключенных сервисов, количество их экземпляров и сколько флагов они используют.
Есть тёмная тема!
Демо-проект
Во время работы над статьей я написал небольшой демо-проект, состоящий из сервиса на Spring, использующего Unleash SDK, и консольного приложения для имитации трафика от разных пользователей.
Для желающих самостоятельно поизучать Unleash, я выложил его в открытый доступ. В проекте уже заранее добавлены все тоглы, настроен сбор метрик и отрисованы графики в Grafana.

Чтобы запустить проект достаточно одной команды:
docker-compose -p unleash-demo up -d
Более подробную инструкцию по запуску и тестовым данным можно найти в README.
На этом у меня все, спасибо за внимание. Если у вас есть опыт применения Unleash на проде, то буду рад, если вы поделитесь им в комментах.