8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
|
"index": 123,
|
|
"slug": "editorial-2024-08-practice-feature-flags",
|
|
"title": "Фича-флаг как контракт выпуска: включить, проверить, удалить",
|
|
"excerpt": "Release-флаг снижает риск только тогда, когда у него есть ограниченная аудитория, безопасный fallback, владелец, срок пересмотра и заранее описанное удаление.",
|
|
"contentHtml": "<p>Проблема с фича-флагом обычно проявляется не в момент включения. В пятницу новую форму checkout включают для небольшой когорты, а в понедельник после ошибки возвращают старую. Ошибка исчезает, но в репозитории остаются две ветки, конфигурация живёт без владельца, а часть пользователей могла сохранить данные нового формата.</p>\n<p>Выключенный флаг уменьшает аудиторию, но не удаляет код и не откатывает побочные эффекты. Поэтому release-флаг нужно рассматривать как временный контракт выпуска: он отвечает на вопрос, кто получает новый путь, какое значение безопасно при сбое, какие сигналы останавливают rollout и когда исчезнут условие, настройка и лишние тесты.</p>\n<h2>Какая задача решается</h2>\n<p>Фича-флаг отделяет доставку кода от выбора поведения во время работы программы. Команда может выложить совместимый код, проверить его на внутренней когорте и только потом расширять аудиторию. Это полезно для релиза, но само по себе не делает новую функцию безопасной: флаг не заменяет миграцию данных, контроль прав, обратную совместимость или план отката.</p>\n<p>До первой строки кода зафиксируйте один вопрос. Формулировка «включить новый checkout» слишком широкая. Проверяемый вариант звучит так: «Показать новую форму внутренней когорте, а при ошибке оценки или рендера оставить прежнюю форму». В такой фразе уже видны субъект, граница решения и отрицательный путь.</p>\n<table><thead><tr><th>Часть контракта</th><th>Что зафиксировать</th><th>Как проверить</th></tr></thead><tbody><tr><td>Вопрос</td><td>Какое поведение меняется и для кого</td><td>Другой инженер пересказывает условие одним предложением</td></tr><tr><td>Владелец</td><td>Кто остановит rollout и примет итоговый вариант</td><td>Имя или команда указаны в карточке изменения</td></tr><tr><td>Аудитория</td><td>Окружение, targeting key и способ удержания когорты</td><td>Один субъект получает ожидаемый вариант повторно</td></tr><tr><td>Fallback</td><td>Путь при ошибке, таймауте и устаревшем состоянии</td><td>Сбой провайдера воспроизведён в изолированной проверке</td></tr><tr><td>Удаление</td><td>Код, настройка, тесты и сигналы, которые исчезнут</td><td>После cleanup поиск по ключу не находит рабочих веток</td></tr></tbody></table>\n<h2>Что именно возвращает провайдер</h2>\n<p>Boolean скрывает причину выбора. В стандарте OpenFeature провайдер разрешает типизированное значение по ключу, default value и контексту оценки, а результат может содержать причину, вариант, метаданные и код ошибки. Приложение должно сохранить эту информацию там, где она нужна для диагностики, но не выводить в логи персональные поля из контекста.</p>\n<p>Контекст оценки содержит сведения, по которым выбирается вариант. В нём есть необязательный <code>targetingKey</code> — строковый идентификатор субъекта. Он может быть идентификатором пользователя, сервиса или тестового субъекта. Если провайдер использует процентное распределение или адресные правила, отсутствие ключа может сделать результат непредсказуемым. Это свойство провайдера, а не гарантия всех систем фича-флагов.</p>\n<p>Условие применимости здесь важнее названия библиотеки. OpenFeature описывает API и жизненный цикл интеграции, но не назначает владельца, срок удаления, процент rollout или безопасный fallback для бизнеса. Эти поля нужно определить в своей change-карточке и проверить средствами конкретного SDK.</p>\n<pre><code>const flagCard = { key: 'checkout-form-v2', kind: 'release', owner: 'checkout-team', environment: 'staging', targetingKey: 'test-subject-001', defaultValue: false, fallbackPath: 'checkout-form-v1', stopConditions: ['5xx increase', 'render error', 'latency budget'], reviewAfter: '2024-08-30', cleanup: ['remove-branch', 'remove-config', 'keep-regression-test'], }; if (flagCard.defaultValue !== false || !flagCard.cleanup.length) process.exit(1); console.log(flagCard.key);</code></pre>\n<p>Сохраните фрагмент в файл <code>flag-card.mjs</code>, затем выполните <code>node flag-card.mjs</code>. Команда проверит, что fallback по умолчанию выключен и список удаления не пуст. Файл не обращается к настоящему провайдеру и не отправляет телеметрию. Поля <code>owner</code>, <code>reviewAfter</code> и <code>cleanup</code> — локальная политика команды, а не часть обязательного API OpenFeature.</p>\n<p>Для рабочего проекта добавьте к такой проверке схему данных и проверку запрещённых значений. Секреты, email и необезличенные атрибуты не должны попадать в карточку или в контекст оценки без отдельного решения о хранении и доступе.</p>\n<h2>Где вычислять решение</h2>\n<p>Решение, зависящее от тарифа, права доступа, состояния счёта, fraud-сигнала или другого защищённого входа, вычисляйте на сервере. Клиент должен получить уже разрешённый presentation value и обработать его как вход, а не повторять правило по урезанному контексту. Иначе сервер и браузер могут выбрать разные варианты.</p>\n<p>Клиентская оценка подходит для публичного оформления, локали или другого решения, для которого все входы уже открыты и потеря точности не меняет права пользователя. Даже в этом случае проверьте семантику кэша, обновления и отсутствие чувствительных данных в evaluation context. Название «client-side» не означает, что конфигурация автоматически безопасна.</p>\n<figure><img src='/assets/editorial/2024/feature-flags-2024-decision-tree.svg' alt='Дерево решения release-флага: от симптома и проверки карточки через границу серверного или клиентского решения к наблюдению и удалению временной ветки' loading='lazy' /><figcaption>Сначала фиксируются владелец, аудитория и fallback, затем выбирается граница оценки. Диаграмма показывает порядок review, а не состояние конкретного сервиса и не обещает результат rollout.</figcaption></figure>\n<h2>Evaluation, exposure и результат — разные события</h2>\n<p>Evaluation означает, что evaluator вернул значение для контекста. Это ещё не показ интерфейса. Между оценкой и результатом могут произойти ошибка API, отказ рендера, закрытие страницы или повторная доставка события.</p>\n<p>Разделите минимум четыре сигнала:</p>\n<ol><li><strong>evaluation.</strong> Запишите ключ флага, вариант или причину, версию конфигурации, окружение и обезличенный идентификатор когорты;</li><li><strong>response.</strong> Проверьте, что сервер вернул допустимый ответ и не скрыл ошибку под успешным HTTP-статусом;</li><li><strong>render.</strong> Зафиксируйте, что выбранный компонент действительно построился без ошибки;</li><li><strong>action.</strong> Отдельно измеряйте действие пользователя или бизнес-результат, если он нужен для решения.</li></ol>\n<p>События провайдера полезны для состояния самого источника: OpenFeature описывает готовность, ошибку, изменение конфигурации и устаревшее состояние. Событие <code>PROVIDER_STALE</code> говорит о свежести кэша, но не доказывает exposure. Аналогично, событие evaluation не доказывает успешный render. В каждом дашборде укажите, какой вывод разрешает сигнал и какой вывод из него делать нельзя.</p>\n<h2>Порядок безопасного rollout</h2>\n<p>Rollout — это последовательность проверяемых шагов, а не одно изменение процента. Сначала убедитесь, что старый путь по-прежнему поддерживается и что оба пути согласованы с форматом данных. Затем расширяйте аудиторию только при выполнении заранее заданных stop conditions.</p>\n<ol><li><strong>Назовите старый и новый путь.</strong> Перечислите чтения, записи, побочные эффекты и совместимость форматов.</li><li><strong>Выберите тип флага.</strong> Не смешивайте release, эксперимент, миграцию данных и постоянную policy в одном ключе: у них разные сроки и критерии успеха.</li><li><strong>Закрепите субъекта.</strong> Используйте стабильный targeting key и проверьте повторную оценку после новой сессии, логина и смены окружения.</li><li><strong>Определите fallback.</strong> Отдельно проверьте default value, ошибку провайдера, таймаут, stale state и частично обновлённую конфигурацию.</li><li><strong>Проведите малый шаг.</strong> Сравните error rate, latency, целостность ответа и ошибки старой ветки с базовым окном. Не приписывайте разницу флагу без одинаковой границы измерения.</li><li><strong>Остановите rollout при нарушении условия.</strong> Верните объявленный fallback, запишите причину и сохраните effective version, чтобы повторить диагностику.</li><li><strong>Зафиксируйте итог.</strong> После выбора варианта заморозьте правило и откройте отдельное изменение cleanup.</li></ol>\n<p>Вариант «внутренняя когорта» не означает автоматически безопасный вариант. Проверьте, что в неё не попадают реальные клиенты, что персональные данные не используются как необязательный targeting input и что оператор может быстро вернуть старое поведение.</p>\n<h2>Отключение не равно удалению</h2>\n<p>После disablement новая аудитория перестаёт получать ветку, но зависимость от флага остаётся. Перед удалением проверьте три слоя: код, конфигурацию и данные. В коде ищите ключ, условные ветки, адаптеры и тестовые обходы. В конфигурации — правила, значения по умолчанию, окружения, кэш и разрешения. В данных — записи нового формата, фоновые задания и потребителей, которые ещё могут их читать.</p>\n<pre><code>rg -n --hidden --glob '!node_modules' 'checkout-form-v2' src test config rg -n --hidden --glob '!node_modules' 'checkout-form-v1|checkout-form-v2' src test config git diff --check pnpm test --filter checkout</code></pre>\n<p>Команды предполагают Unix-подобную оболочку, установленный <code>rg</code>, рабочий скрипт тестов и каталог <code>src</code>. Подставьте имена своего репозитория. Первый поиск показывает остатки ключа, второй помогает найти обе ветки до удаления. Если новая ветка записывала данные, добавьте проверку чтения старого и нового формата; одного поиска и зелёного unit-теста недостаточно.</p>\n<p>Надёжный cleanup оставляет тест поведения выбранного варианта, но удаляет временную развилку и её настройку. Отдельно обновите runbook и мониторинг. Если поиск всё ещё находит production-условие по ключу, удаление не закончено.</p>\n<h2>Ограничения применимости</h2>\n<p>Эта схема рассчитана на release-флаг с обратным выбором поведения. Она не описывает полноценный эксперимент: для него нужны рандомизация, заранее выбранная метрика, длительность окна и статистический план. Она также не является планом отката базы данных и не решает проблему несовместимой схемы.</p>\n<p>Интервалы обновления, поведение при сетевой ошибке, кэш и формат resolution details зависят от провайдера и SDK. Нельзя переносить настройки одного продукта в другой по названию поля. Для OpenFeature гарантии относятся к контракту API; owner, review date, stop condition, redaction и cleanup команда должна определить и протестировать самостоятельно.</p>\n<p>Не передавайте в контекст оценки больше данных, чем нужно правилу. Официальная документация OpenFeature отдельно предупреждает о том, что провайдер может сериализовать или сохранять контекст. Используйте стабильный обезличенный ключ, когда провайдер и требования к аудиту это допускают, и проверьте политику хранения у выбранного сервиса.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://openfeature.dev/specification/sections/evaluation-context/' target='_blank' rel='noopener'>OpenFeature Specification: Evaluation Context</a> — targeting key, пользовательский контекст и порядок объединения контекстов.</li><li><a href='https://openfeature.dev/specification/sections/providers/' target='_blank' rel='noopener'>OpenFeature Specification: Provider</a> — default value, типизированная оценка, resolution details и ошибки провайдера.</li><li><a href='https://openfeature.dev/specification/sections/events/' target='_blank' rel='noopener'>OpenFeature Specification: Events</a> — состояния READY, ERROR, CONFIGURATION_CHANGED и STALE.</li><li><a href='https://openfeature.dev/specification/appendix-d/' target='_blank' rel='noopener'>OpenFeature Specification: Observability</a> — поля результата оценки и границы телеметрии.</li></ul>\n<h2>Критерий готовности</h2>\n<p>Флаг готов к включению, если инженер может без устного контекста назвать вопрос, старый и новый путь, owner, аудиторию, targeting key, серверную или клиентскую границу, fallback, stop conditions, сигналы и дату review. Флаг готов к удалению, если выбранный вариант зафиксирован, совместимость данных проверена, поиск не находит временных веток, а тесты больше не зависят от старого ключа.</p>\n<p>Если не известен хотя бы один из этих ответов, добавление ещё одного boolean не уменьшит риск. Сначала верните изменение на review и заполните контракт.</p>"
|
|
}
|