Skip to main content
Uma flag responde “isso já foi entregue para essa pessoa”. É uma pergunta de release sobre o seu produto — não “o plano dela permite”, que é entitlements, nem “o assento dela permite”, que são papéis e permissões. Manter os três separados é por que checar uma flag é grátis e por que servir uma nunca é cobrado: um kill switch que parasse de funcionar porque uma fatura atrasou falharia no pior momento possível.

Lendo uma

isEnabled e useFlag são síncronos e devolvem um booleano puro. Isso é o contrato, não um detalhe de implementação. Um hook que começasse em undefined renderizaria a ausência de toda funcionalidade protegida por um quadro e a funcionalidade no seguinte — e “ainda não sabemos” e “desligado” são idênticos para a pessoa, enquanto só um dos dois é seguro. Uma flag que pisca ligada é pior que uma simplesmente desligada. Uma chave desconhecida é false. Uma flag que ninguém definiu é uma funcionalidade que ninguém entregou, e uma página que nunca chamou boot()false para tudo, que é a resposta honesta: ninguém decidiu a audiência dessa pessoa.

Do que uma flag é feita

null e 0 serem diferentes é o tipo de distinção que esta API mantém em vez de colapsar: “sem rampa” e “com rampa, no zero” são estados diferentes de um release, e guardar 100 para o primeiro perderia o segundo. Uma flag mira um segmento e nunca um filtro próprio. É a regra que impede audiências de serem reconstruídas módulo a módulo — tudo o que você consegue dizer sobre quem vê uma funcionalidade, você diz uma vez, num segmento, e todo outro módulo lê o mesmo.

A porcentagem, e qual string ela hasheia

Uma rampa faz bucket pelo id do contato assim que a pessoa está identificada, e pelo id anônimo do dispositivo enquanto ela é uma visitante. Os dois são estáveis enquanto o que eles nomeiam for. Cada flag carrega o próprio sal, então estar no décimo azarado de uma rampa não diz nada sobre a próxima — um sal compartilhado faria toda rampa de 10% acertar o mesmo décimo dos seus usuários, que passariam a experimentar seu produto como permanentemente quebrado enquanto ninguém mais conseguiria reproduzir nada do que eles relatassem. Um flicker é real e está dito em vez de escondido. Uma visitante anônima faz bucket pelo dispositivo; no momento em que assina, o mesmo humano faz bucket por um id de contato — string diferente, portanto possivelmente bucket diferente, portanto uma rampa em que ela estava pode deixá-la de fora um carregamento depois. Isso é aceito porque as alternativas são piores. Fazer bucket de todo mundo pelo id do contato não evitaria (uma visitante anônima tem uma linha de contato, e assinar faz o merge dela, então o id muda de qualquer jeito). Carregar o id anônimo para sempre faria a audiência de uma rampa depender de um cookie que a pessoa pode limpar, o que transforma “10% dos usuários” em “10% dos navegadores, re-amostrados sempre que alguém limpa o histórico” — uma porcentagem que deriva em silêncio é pior que uma que muda uma vez, num momento em que a pessoa já está vendo a página mudar. O que decorre disso para você é uma frase e não um mecanismo: não coloque um checkout atrás de uma flag de porcentagem para tráfego deslogado.

O kill switch, e o atraso honesto dele

O valor da flag para esta pessoa — segmento, porcentagem, tudo — é decidido pelo boot(), na mesma ida e volta que a sessão já custou. Depois disso, o cliente faz poll de um documento público:
Ele leva o que é verdade para todo mundo — o interruptor — e nada sobre quem. Aplicado sobre o snapshot do boot, ele só sabe tirar uma flag:
  • desligar uma flag alcança uma página aberta em cerca de quinze segundos, sem nenhum refresh de sessão envolvido;
  • alargar uma rampa, ou re-mirar um segmento, vale a partir do próximo boot.
Essa assimetria é o desenho. A direção urgente é desligar — “o checkout está quebrado, desliga agora” é uma frase com relógio junto, e “todo mundo do segmento novo deveria receber isso” não é. É também por que uma flag não é claim de JWT. Um token de cinco minutos torna a resposta eventual por construção, e um kill switch que não pode valer por cinco minutos não é um kill switch. Um push — um socket, um stream — seria mais rápido e é a troca errada neste tamanho: uma conexão por página aberta, mantida através de cada deploy, cujo modo de falha é o seu produto silenciosamente parar de ouvir sobre mudanças e cuja recuperação é um poll de qualquer forma. O documento é cacheável, endereçado pela publishable key que sua página já tem, e seguro atrás de um CDN. version é opaca — compare por igualdade para saber que nada se moveu, nunca por ordem.

O que acontece quando a rede não está lá

Um refresh que falha mantém o último documento. Ele nunca esvazia as flags. Um kill switch que abre sempre que a rede cai é o oposto do que ele existe para fazer, e degradar para “sem flags” durante um minuto ruim faria seu produto voltar ao estado pré-flag para todo mundo — nossa indisponibilidade virando a sua, por causa de uma funcionalidade que você ligou semana passada e não pensa mais nela. O documento leva stale-if-error ao lado do max-age pelo mesmo motivo. O poll roda apenas enquanto a página está visível; uma aba em segundo plano não é uma página onde alguém está prestes a ser surpreendido. Uma flag arquivada some do documento, e uma chave que não está nele é false. Arquivar é como você encerra uma rampa da qual desistiu — para quem lê, é indistinguível de um delete, que é o que se quer.

No servidor

Não há intervalo nem visibilidade para se apoiar, então refreshFlags() é a leitura explícita — para um servidor, para um teste, ou para o momento logo depois de um ato que você sabe ter mudado algo.
Com @userkit/nextjs o mesmo documento é buscado pela sua própria origem, então a publishable key fica fora do bundle.

No painel

Engagement → Flags, atrás de flags:manage — owner e admin por padrão. Uma permissão cobre a superfície inteira aqui, diferente de segmentos, onde ler é customers:read porque a leitura de um segmento é uma lista filtrada de contatos. A leitura de uma flag não é a vista de nada que outra permissão já mostre: é o estado de release do seu produto, então ela não tem outra casa. Ela é uma permissão separada de segments:manage justamente porque as duas são vizinhas. Definir uma audiência é descrever pessoas; virar uma flag é entregar ou desentregar uma funcionalidade no minuto em que ela é virada, para quem estiver com a página aberta. Uma organização que quer seu analista de growth construindo audiências sem poder desligar o checkout diz isso concedendo uma e não a outra. Virar uma flag publica flag.updated no barramento interno, e esse fato deliberadamente não sai do prédio como webhook: ele nomeia o estado de release de um ambiente, e o documento já é a forma suportada de saber de uma mudança. O sal do bucket nunca é devolvido pela API. É o único valor que permitiria a alguém calcular em que bucket está e então escolher um identificador que caia dentro de uma rampa.