Os Padrões dos Gigantes da Web - o Feature Flipping
O padrão “Feature Flipping” permite ativar e desativar as funcionalidades diretamente em produção sem a necessidade de subir uma nova versão.
Os Gigantes da Web usam vários termos para definir esse padrão: o FlickR e a Etsy falam de “Feature Flags”, o Facebook escolheu “Gatekeepers”, “Feature buckets” para o Forrst, “Features bits” na Lyris inc. e o Martin Fowler fala de “Feature toggles”.
Cada um usa um nome e uma implementação diferente mas os objetivos são os mesmos. Nesse artigo, vamos explicar como implementamos o “Feature Flipping” em nossa loja de applicativos: Appaloosa, trazendo várias vantagens e também algumas regras.
Implementação
O mecanismo é muito simples, só tem que condicionar a execução do código de uma funcionalidade:
if Feature.is_enabled(‘new_feature’)
# do something new
else
# do same as before
end
A implementação do método “is_enabled”, por exemplo, vai verificar um arquivo de configuração ou consultar em um banco de dados para saber se a funcionalidade está ativa ou não.
Um dashboard de administração geralmente ajuda no processo de troca quente (Hot-Swap) dos status dos diferentes “flags”.
Deploy Contínuo
Uma das grandes vantagens de ligar e desligar as funcionalidades de maneira “Hot-Swap” é a capaciade de entregar o aplicativo em produção de maneira contínua (Deploy Contínuo).
Geralmente, as organizações que implementam o Deploy Contínuo encontram o seguinte problema: Como fazer um commit no repositório de código e assegurar, ao mesmo tempo, a estabilidade do aplicativo que será entregue em produção?
No caso de um desenvolvimento de funcionalidades que precisam de mais de um dia para ser terminadas, fazer o commit somente quando o desenvolvimento é terminado (depois de alguns dias) é contrário ás boas práticas de desenvolvimento com integração contínua. Mas a existência de tempo entre dois commits e mais os merges, vai ser difícil e arriscado, e a necessidade de fazer algum refactoing vai ser mais limitada. Com esse impedimento, fica duas possibilidades:
“Feature branching”: criar um “branching” com a ferramenta de versionamento e fazer merges de código;
“Feature flipping”: criar um “branching” diretamente no código.
O debate sobre essas dois enfoques é animado e vocês poderão encontrar algumas opiniões aqui ou aqui.
O Feature Flipping permite ao desenvolvedor programar dentro do seu “if”, e assim fazer um commit de um pedaço de código que ainda não está terminado e este pedaço não fica funcionando, na condição que o código pode ser compilado e passará nos testes. Os outros desenvolvedores podem recuperar e utilizar a ultima versão sem problema se eles não ativarem as funcionalidades que estão sendo desenvolvidas. Da mesma maneira, pode ser feito o deploy do código em produção, com a condição da funcionalidade ainda não estar ativa. Quando a funcionalidade é terminada, só precisa ativar o código trocando o seu “flag” no dashboard de administração ou no arquivo de configuração.
Isso é o grande diferencial dessa estratégia: quebrar a dependência entre entregar o código em produção e entregar novas funcionalidades completas em produção que costumam andar juntos.
Por extensão, é possível sincronizar a ativação da funcionalidade com uma campanha de marketing. Desse forma, limitamos os riscos de subir uma nova versão o dia D.
Segurar os deploys
Um dos ganhos mais interessante desse padrão é que ele ajuda a assegurar os deploys. Da mesma maneira que é simples ativar uma funcionalidade, fica fácil de desativá-la e não precisa ter um processo de “rollback” para voltar a versão N-1.
Podemos rapidamente cancelar a ativação de uma nova funcionalidade se os testes em produção ou os feedbacks dos usuários são negativos.
Experimentar para melhorar o produto
Uma evolução natural do Feature Flipping é a capacidade de ligar/desligar algumas funcionalidades para populações diferentes:
Um grupo de usuários “cobaia”: que vão dar feedbacks sobre as novas funcionalidades;
Os outros usuários: que usaraão a versão anterior até que a funcionalidade seja ativada para todos.
O código será parecido com apresentado abaixo:
if Feature.is_enabled_for(‘new_feature’, current_user)
# do something new
else
# do same as before
end
Esse mecanismo permite, por exemplo, testar a performance de uma nova implementação comparando os resultados de diferentes populações. As medições dos resultados podem ajudar a identificar quais são as implementações mais eficientes.
De uma outra forma, o Feature Flipping é a ferramenta ideal para executar os testes A/B.
Propor um produto personalizado
Em algumas situações, é interessante deixar o usuário escolher as opções que ele quer utilizar: no exemplo da gestão dos anexos do Gmail: o usuário pode desistir de usar as funcionalidades avançadas (Drag & Drop, upload de vários anexos ao mesmo tempo, etc.) se ele encontrar alguma dificuldade e preferir usar o modo “básico” dessa funcionalidade.
Ao contrario, podemos oferecer o uso do modo “avançado”: os “Labs” do Gmail são um caso de uso do Feature Flipping.
Para isso, basta uma interface para o usuário escolher as opções que ele deseja usar.
Gerar as funcionalidades taxadas
A ativação de funcionalidades taxadas, relatadas com diferentes planos, pode ser difícil de implementar. Geralmente acaba com código da seguinte forma:
if current_user.current_plan == ‘enterprise’ || current_user.current_plan == ‘advanced’
# paid feature code
end
O problema é que pode ficar mais complicado quando exceções desse tipo surgirem:
uma empresa tem o plano “basic” mas eles vão usar uma funcionalidade do plano “advanced” de graça;
ou a funcionalidade estava disponível no plano “advanced” dois meses atrás, mas agora, o marketing quer que ela fica disponível somente para os clientes do plano “enterprise”... e os que já utilizam.
O Feature Flipping pode ajudar para resolver essas situações sem gerar exceções no código. Só precisa ativar as funcionalidade quando o usuário escolher um plano. Desse modo, não são planos que serão gerados mas as funcionalidades que os clientes podem usar com o dashboard de administração.
“Graceful degradation” ou degradação graciosa
Algumas funcionalidades são mais críticas que outras para o negócio. Quando tem cargas mais altas na infraestrutura técnica (Black Friday para um site e-commerce por exemplo), faz sentido favorecer algumas em detrimento de outras.
Infelizmente, é geralmente difícil ter no aplicativo a função de desligar a “funcionalidade de consulta dos gráficos de síntese”… exceto se essa “funcionalidade de consulta dos gráficos” implementa o Feature Flipping.
Já falamos que é importante medir. Quando tem medições, fica bem fácil desligar uma funcionalidade, por exemplo “o tempo de reatividade médio é superior á 10 segundos durante 3 minutos”.
Isso permite degradar progressivamente as funcionalidades do aplicativo para favorecer a experiência dos usuários na utilização das funcionalidades essenciais.
Essa ideia de “degradação graciosa” é parecida com o padrão de “Circuit breaker” - que o Michel Nygard fala no seu livro « Release It! » - para desligar uma funcionalidade quando um serviço não responde.
Limites e constrangimentos
Como foi explicado, a implementação é feita com alguns “if” no código. Mas como todo desenvolvimento, pode rapidamente virar complexo se não tomarmos algumas precauções.
1 “if” = 2 testes
Os testes automatizados são a melhor maneira de assegurar o bom funcionamento do seu aplicativo. No caso do Feature Flipping, precisará de, pelo menos, dois testes para:
testar a funcionalidade quando está desligada;
testar a funcionalidade quando está ligada.
Quando estamos desenvolvendo, costumamos esquecer o teste com a funcionalidade desligada mas é isso o que os usuários vão ver até que a funcionalidade seja ligada. Nesse caso e como sempre, o TDD bem aplicado ajuda a não esquecer esses testes.
Fazer a faxina
O uso intensivo do Feature Flipping pode acabar com um acumulo de “if” deixando o código cada vez mais difícil de manter. No entanto, alguns desses “if" só ficaram para assegurar o Deploy Contínuo.
Para todas essas funcionalidades que não deveram mais ser desligadas (funcionalidades não taxadas/opcionais, e que não serão degradadas porque são críticas ao negócio), é importante retirar os “if” declarados, a fim de reduzir o código e que o mesmo seja de mais fácil manutenção.
Tem que se planejar em “fazer a faxina” depois de entregar uma nova versão. Como todas tarefas de refactoring, essas são ainda mais leves que justas.
Impactos pesados - mudanças no modelo de dados
Algumas funcionalidades tem mudanças pesadas no código e no modelo de dados.
Por exemplo, uma tabela Pessoa (“Personne”) que tem um campo Endereço (“Adresse”). Temos que evoluir esse modelo para ter tabelas separadas:
Para gerar esse tipo de situação, aqui tem uma estratégia que pode ser utilizada:
Adicionar a tabela Endereço (“Adresse”);
Atualizar o aplicativo para que ele use as duas tabelas;
Migrar os dados existentes e tirar o campo Endereço da tabela Pessoa;
O aplicativo ainda não mudou para os usuários mas já usa o novo modelo de dados;
Começamos os desenvolvimentos das funcionalidades (usando o Feature Flipping) que precisaram dessas atualizações no modelo de dados.
Esse enfoque é simples mas precisa interromper o aplicativo para entregar nos estágios 2 e 4.
Impactos sem interrupção
Outras técnicas são possíveis para gerar várias versões de modelo de dados ao mesmo tempo. Essa práticas vem com o padrão “Zero-Downtime Deployment” (em Francês) e permite atualizar o modelo de dado sem impactar a disponibilidade do aplicativo. Usando scripts, triggers para sincronizar os dados, ou views para ter uma camada de abstração que será usada pelo aplicativo. Bem menos freqüente que as mudanças de código, as mudanças de modelo relacional são complexas e devem ser antecipados e efetuadas com cuidado.
Eles já fazem Deploy Contínuo
O Feature Flipping funciona bem para nós, mesmo se ainda não somos um Gigante da Web. ;)
No projeto Appaloosa, usamos os diferentes padrões explicados nesse artigo.
Os Gigantes da Web tem obstáculos quanto a tamanho, deploy em diferente localizações, migração com grande volume que forçam eles a implementarem e usarem esses mecanismos. Podemos citar Facebook, FlickR ou Lyris inc. Entre nossos clientes, podemos citar Meetic e a BNF - Bibliothèque Nationale de France - que usam o Feature Flipping.
E na minha empresa?
Como vimos, a implementação é muito simples e esse padrão pode ser usado bem rapidamente. Mas, existem implementações em diferentes linguagens:
a gem rollout em Ruby;
Feature-Flipper em Grails;
FF4J em Java;
etc.
Mas é tão simples, que você provavelmente vai querer a sua própria implementação para obter o que precisa.
As vantagens e os usos são vários, então se você quiser entregar funcionalidades progressivamente, testar com grupo de usuários ou fazer um Deploy Contínuo só precisa começar.
Fontes
Les Géants du Web: site em Francês dedicado as práticas dos Gigantes da Web