Feature flags: como proteger seu software de catástrofes - codamos.com.br
Nawarian 10 maio 2022 tecnica
Recentemente o Codamos recebeu um artigo do Rouan Wilsenach sobre o conceito de Ship / Show / Ask, que basicamente diz que não faz muito sentido esperar seu time aprovar um Pull Request pra integrar o seu código na maior parte das vezes.
A intenção é boa, mas muitas de nós ainda fica com um pé atrás: como garantir a qualidade do software se o Code Review acontece depois de lançar pra produção?
Neste artigo eu vou te apresentar uma técnica mágica que vai mudar para sempre a forma como você lida com código nas suas empresas: as feature flags!
O que é feature flag ou feature toggle?
Que problemas feature flags resolvem?
Proteger lançamentos de produtos
Proteger-se de catástrofes
Que problemas feature flags criam?
A complexidade dos testes é maior
Depurar e reproduzir situações descritas em logs
Gerenciamento e governança de software
Como eu integro feature flags no meu software?
Implementação própria
Feature flag, também conhecida por feature toggle, é uma técnica que lhe permite proteger parte do seu código de forma dinâmica. É como se você pudesse colocar um interruptor no seu código, e só quando você virar a chave o novo código passa a valer.
Na prática a feature flag te permite adicionar alguns IFs no seu código, que vão executar uma ou outra lógica dependendo do estado desta feature flag. Dá uma olhada no exemplo abaixo 👇!
Digamos que o método Users.list() atualmente chama uma API rest no endpoint /v1/users e o mantenedor desta API disse que a versão 2 está disponível através do endpoint /v2/users e precisa ser adotada, ao final do mês a versão 1 será descontinuada.
A migração parece bem simples e direta. Mas é sempre bom manter-se longe de problemas. Uma forma simples e livre de muitas preocupações para implementar esta mudança é através de uma feature flag. Olha o exemplo abaixo:
Código exemplificando a utilização de feature flags. Transcrição a seguir.
if (isFeatureActive('enableUserListV2API'):
return self.listV2()
return self.listV1()
def listV1():
Call /v1/users
def listV2():
Call /v2/users
A função isFeatureActive() retorna TRUE or FALSE, e o ideal é que possamos configurar o retorno desta função. Talvez escrevendo num banco de dados, talvez escrevendo num arquivo...
Acho que deu pra entender bem a ideia de feature flag já. Vou deixar um vídeo (em inglês) abaixo que explica com palavras e imagens como feature flags funcionam caso esteja interessade em se aprofundar no assunto.
O interessante sobre este vídeo é exatamente a ideia de fazer deploy de forma intencional. A filosofia por trás disso é basicamente a mesma que eu mostrei no meu artigo sobre programação por coincidência X por intenção.
De acordo com Pete Hodgson, num artigo que publicou no blog do Martin Fowler, as feature flags resolvem diferentes categorias de problemas:
As vezes a pessoa que faz a gerência de produto gostaria de testar o produto em produção algumas semanas ou dias antes de realizar o lançamento oficial. E o ideal seria poder lançar o produto sem que uma pessoa desenvolvedora esteja envolvida.
De forma que uma feature flag é a opção perfeita para resolver o caso acima. A pessoa desenvolvedora pode criar uma flag chamada habilitarNovoCheckout, por exemplo, e utilizá-la em todo o código novo. A pessoa que gerencia o produto pode a qualquer momento ativar ou desativar o novo checkout apenas apertando um botão liga/desliga numa interface.
Código exemplificando a utilização de feature flags para o lançamento de produtos. Transcrição a seguir.
<CheckoutFlow>
isFeatureActive('habilitarNovoCheckout') ? (
<NovoCheckoutFlow {...props} />
<AntigoCheckoutFlow {...props} />
</CheckoutFlow>
O bacana de feature flags é que elas não precisam levar em consideração apenas o estado ligado/desligado. A decisão sobre uma feature flag estar ativa ou não pode ser bem mais complexa e levar em consideração elementos como:
ID / Sessão do usuário
De forma que você pode configurar uma flag para estar ativa somente para usuários da cidade de São Paulo acessando o site aos finais de semana entre 10am e 3pm. Tudo depende da ferramenta que você utilizar para feature flags, mas acho que dá pra entender o potencial.
Por mais que nós escrevamos código utilizando TDD, que façamos revisão de código com nossos pares, e adotemos um processo rígido de qualidade de software, nada garante que o nosso código não vá causar algum tipo de anomalia.
Uma funcionalidade talvez acabe sendo tão popular que muito mais pessoas do que o esperado a querem utilizar, aumento a carga no servidor. Ou talvez você tenha reparado que um algoritmo não está fazendo exatamente o que deveria.
Ao notar um problema desta dimensão, você pode facilmente desligar uma feature flag e investigar a solução de forma controlada. Sem precisar reverter algum deployment ou, pior ainda, um commit.
Algumas funcionalidades precisam ficar protegidas. E os sistemas de ACL nem sempre são suficientes.
Por exemplo, digamos que a área de gerenciamento da sua plataforma precise ser acessada somente por computadores dentro da VPN da empresa. Uma feature flag que verifica a origem do IP pode facilmente resolver esta verificação sem nenhuma adição ao seu framework de ACL.
Como você já deve ter imaginado, feature flags adicionam complexidade ao seu código. Ainda mais considerando que você pretende testar seus códigos, certo?
Vejamos o snippet abaixo:
function sumPositive(int|float a, int|float b): int|float
if (typeof(a) !== typeof(b):
raise "Cannot add an integer to a float"
Eu esperaria ver ao menos três testes para esta função, de forma a cobrir os seguintes casos:
Soma de dois floats
Se nós adicionar uma feature flag que nos impede de somar números negativos, o código rapidamente se transformaria em algo assim:
if (isFeatureActive('disableNegativesSum') && (a < 0 || b < 0)):
raise "Summing negative numbers is not allowed"
Ao adicionar uma feature flag nós precisamos agora adicionar dois outros testes:
O comportamento quando a feature flag está inativa
O comportamento quando a feature flag está ativa
A complexidade se reflete não somente nos testes, mas também ao depurar uma determinada chamada de método ou página.
Além disso, olhando apenas para os logs de acesso não é tão simples identificar se uma determinada flag estava ativa no momento ou não.
Com o tempo, é ideal remover as feature flags que já não têm mais nenhuma utilidade. A cada vez que você cria uma feature flag nova, você está também criando um novo caminho em seu software que precisará ser limpo depois.
O mau gerenciamento de feature flags pode explodir seu software em complexidade e dificultar a manutenção deste ao longo do tempo se a sua equipe não limpar estas flags frequentemente.
Portanto ao criar uma feature flag nova é ideal sempre ter em mente quando e como você pretende se livrar desta flag.
Aqui onde eu trabalho, uma das técnicas que a gente adota pra limpar feature flags é gerar um dashboard que mostra todas as flags ativas por data e com a possibilidade de marcar cada flag como "pronta para ser removida". Com base neste dashboard a gente consegue escrever tarefas para fazer esta limpeza.
Algumas empresas – onde eu trabalho, por exemplo – preferem criar o próprio software de gerenciamento de feature flags. Outras adotam soluções de mercado já prontas, e outras ainda preferem utilizar plataformas de código aberto.
As três formas de implementação são validas e você precisa entender bem qual o caso de uso que você precisa cobrir.
Migrar de uma solução para outra é relativamente simples, dado que o código normalmente vai chamar uma função ou método chamado isFeatureActive() ou alguma coisa assim.
O único desafio numa migração é justamente mover os dados de uma solução para outra e manter os SDKs compatíveis.
O bacana de usar uma solução paga é que normalmente tudo já funciona sem precisar configurar muita coisa, e normalmente as empresas oferecem suporte.
O que eu acho particularmente zoado é que no evento de um ataque à plataforma paga, o seu software pode ficar parcialmente disponível ou, pior ainda, ter o comportamento aleatoriamente bagunçado.
Plataformas conhecidas de gerenciamento de feature flags incluem LaunchDarkly™ e Optimizely.
Pacotes de código aberto são uma ótima opção porque normalmente exigem pouca configuração/preparo. Mas adiciona complexidade à sua infraestrutura que precisa de constante manutenção.
Dentre os pacotes mais conhecidos estão o Unleash e Flipper.
A implementação própria normalmente parece a melhor opção durante os primeiros meses/anos. O motivo é que no começo você provavelmente vai identificar vários casos de uso complexos que são muito específicos do domínio da aplicação.
Com o tempo, o número de alterações no software de gerenciamento de flags vai diminuir até o ponto em que este pedaço de software entra em modo de manutenção.
Neste momento eu acho que faz muito mais sentido procurar uma solução paga ou de código aberto, para substituir a implementação própria.
O problema de uma implementação própria é que a partir do momento que ela não dá mais problemas, dificilmente a empresa irá revisitar a implementação, corrigir bugs ou identificar problemas de segurança.
Um serviço pago ou de código aberto te fornece atualizações constantes e melhorias no software.
Feature flag é uma técnica incrível para proteger seu código de diferentes formas: lançamento de produtos independente do envolvimento de pessoas desenvolvedoras, proteger e recuperar de catástrofes e remover completamente a necessidade de Pull Requests antes de lançar o código para produção usando o processo Ship / Show / Ask.
Utilizar feature flags aumenta a sua flexibilidade, mas vem com um custo alto de manutenção.
Por fim, este é um dos passos essenciais para a tão sonhada Integração e Entrega Contínua (CI/CD). Sem um bom monitoramento e ferramentas para forçar curto-circuitos (como feature flags lhe permitem), CI/CD pode ser extremamente perigoso.