Бороздя просторы космоса Хабра, рабочих репозиториев и не только, в сегменте Java разработчиков и других JVM динозавров, была обнаружена извественная проблема, большинство фич закрыты фича-флагами в виде простых переменных в коде (иногда чересчур замедруенными). И в этом хаосе родилась идея просто менеджера флагов для разных приложений.
В мире кубов и контейнеров JVM приложения чувствуют себя немного странного когда речь заходит о вопросах: кто сожрал все ресурсы в кластере? или как же мне вывернуть приложение чтобы не рестартить его? Со вторым вопросом предлагаю ознакомится ближе.
Механизм рефреша конфигураций есть:
-
В спринге в виде /actuator/refresh, но он не гарантирует консистентность между всеми потоками приложения, риск race conditions и деградации приложения
-
JMX — практически невозможен в продовой среде для наших целей
-
Jolokia — риски безопасности, часто в read‑only в проде
Многие и многие менеджеры и продакты отдали бы многое за систему которая в рантайме изменяет поведение системы в один клик, да еще и с раскаткой на n% юзеров! (все же ведь любят канареек?)
Так о чем я, родилась идея легковесного хранилища/менеджера флагов для любых бэк-енд приложений, мобилок или фронт-энда через REST/GRPC взаимодействие.
Что есть что?
По своей сути система состоит из двух отдельных приложений:
1. evaltuation‑api
Отвечает за чтение состояния флагов, их правил и является элементом системы на которую подписываются клиенты ожидающие изменений во флагах. Хранит в кэше состояние флагов.
Подпись естественно в рамках GRPC (потому что так проще для начала)
2. core‑engine
Отвечает за создание, изменение, удаление флагов на условном дашборде или api. Является центровым в этой архитектуре, поскольку:
а) менеджит состояние
б) пушит через канал (pg‑notify so far) обновление напрямую в evaluation‑api
Структура флага проста как мир
create table flags( id uuid default gen_random_uuid() not null primary key, key text not null unique, description text, enabled boolean default false not null, default_value boolean default false not null, created_at timestamp with time zone default now() not null, updated_at timestamp with time zone default now() not null, status text default 'draft'::text not null constraint flags_status_check check (status = ANY (ARRAY ['draft'::text, 'active'::text, 'inactive'::text, 'deprecated'::text, 'archived'::text])));
-
key — самое важное о чем спрашивают подписанные клиенты
-
status — простой статус для жизненным циклом флага
-
default_value — с каким статусом создался флаг
-
enabled — свойство включен ли флаг?
У флагов предусмотрены опциональные условия, которым должены удовлетворить параметры передаваемые клиентами
create table targeting_rules( id uuid default gen_random_uuid() not null primary key, flag_id uuid not null references flags on delete cascade, priority smallint not null, attribute text not null, operator text not null, value jsonb not null, return_value boolean not null, created_at timestamp with time zone default now() not null, unique (flag_id, priority));
-
flag_id — с каким флагом связано правило
attribute — название параметра сравнения -
operator — eq, neq, in, not_in,
-
value — целевое значение аттрибута
-
return_value — что вернуть если правило совпало, пока boolean, возможны расширения до любого типа
В базовом виде вот и весь состав системы и флагов, клиент передает в evaluation‑api набор параметров, evaluation‑api получает данные о флаге, если их нет в кэше из бд, кеширует и отдает клиенту его значение. В случае обновления core‑engire через pg_notify пушит изменения флага в evaluation‑api и тот в свою очередь кеширует их, либо пушит изменения подписчикам GRPC.
На подходе rollout с реализацией консистентного хранения флагов для клиентов, нельзя же просто использовать random() < 0.1, поэтому чтобы один пользователь не всегда попадал в одну и ту же группу на всех флагах придется искать подход, или спрашивать у нейронки умных людей.
ссылка на оригинал статьи https://habr.com/ru/articles/1029024/