Habilitação dinâmica de funcionalidades em sistemas

Jean Meira
Livelo
Published in
7 min readJul 6, 2023

Sistemas modernos estão cada vez mais suscetíveis a mudanças. A maior parte dos negócios depende fortemente de tecnologia, e para garantir o dinamismo necessário para as empresas se manterem competitivas no mercado, é necessário que os sistemas se adaptem rapidamente a fim de viabilizar redução no time-to-market de novos produtos ou funcionalidades.

Porém, a necessidade de construir soluções robustas, preparadas para absorver mudanças de negócio com elegância, já é uma preocupação antiga. Mesmo antes da construção e divulgação do manifesto ágil, já se percebia a necessidade de estar receptivo a mudanças. Tecnologia precisa assumir o papel de aliada e habilitadora do negócio, e isso inclui não ser um gargalo na hora de introduzir novas capacidades nos produtos digitais.

Mudanças nos requisitos são bem-vindas,
mesmo tardiamente no desenvolvimento.
Processos ágeis tiram vantagem das
mudanças visando oferecer vantagem competitiva para o cliente.

Desde a criação do manifesto ágil, muito se evoluiu em engenharia de software. Com o movimento DevOps e a introdução e evolução de práticas de entrega contínua de software, aplicações modernas podem ser modificadas com uma frequência muito superior em relação aos sistemas antigos, utilizando esteiras responsáveis pela entrega rápida de software por diferentes ambientes, com etapas de garantia de qualidade, manutenibilidade, segurança e rastreabilidade.

De acordo com o relatório State of Devops de 2022 do Google, empresas de alto desempenho entregam software em produção sob demanda várias vezes por dia. Mas esse alto volume de entregas em produção não é facilmente obtido. A empresa precisa ter um alto nível de maturidade em diversas disciplinas, como observabilidade, testes, design de soluções e etc. Por isso, é necessário introduzir uma série de técnicas dentro dos times para poder ter confiança no processo sem a necessidade de se utilizar mecanismos burocráticos para liberar a mudança de aplicações em produção.

Dentre as técnicas frequentemente utilizadas destaca-se a aplicação de feature toggles (também conhecidas como feature flags). De forma simplificada, feature toggles permitem que funcionalidades novas sejam implementadas de forma progressiva em sistemas já existentes sem gerar mudanças reais, visto que até a nova funcionalidade ser habilitada, o código não deve fazer efeito.

Além disso, mesmo após a ativação da nova funcionalidade, o risco pode ser muito menor, visto que, mediante resultados inesperados, a desativação da funcionalidade através de feature toggles também deve ser simples, principalmente se comparada a um processo de rollback, que tende a ser mais lento e custoso.

Este artigo abordará as principais características de feature toggles, os diferentes tipos e as preocupações necessárias para aplicar essa técnica sem gerar riscos desnecessários e sem comprometer a manutenibilidade das aplicações.

Diferentes tipos de feature toggle

Frequentemente, feature toggles são utilizadas sem distinção de acordo com o objetivo de cada uma. Isso pode ser um risco, dado que cada perfil exige um tipo de preocupação, e o gerenciamento do ciclo de vida delas pode ser totalmente diferente entre si. Pete Hodgson categoriza feature toggles da seguinte forma:

Release Toggles: utilizadas para habilitar o lançamento de uma nova funcionalidade independente do deploy do código necessário para que ela exista. Normalmente, elas não precisam continuar a existir após o lançamento e validação da nova funcionalidade em escala. Caso seja necessário manter o toggle por motivos operacionais, como para possibilitar o desligamento de uma funcionalidade por fatores externos, ela pode ser tratada com as premissas elencadas em Ops Toggles.

Experiment Toggles: utilizadas para realizar experimentos em ambiente produtivo, como testes A/B. O uso é recomendado para viabilizar melhorias na experiência do usuário através da comparação de resultados entre duas interfaces diferentes. É possível avaliar, por exemplo, a hipótese de alterar o posicionamento de elementos com o intuito de aumentar a conversão em um e-commerce. Caso metade dos usuários da plataforma veja um layout, e a outra metade veja outro, é possível coletar os dados de navegação e escolher a abordagem de maior sucesso com base em dados, e não em opiniões. Esse tipo de toggle pode ser removido após a finalização do experimento.

Ops Toggles: utilizadas para viabilizar o controle de aspectos comportamentais do sistema. É possível que a configuração permita desligar alguma funcionalidade mediante problemas, evitando prejudicar a experiência do usuário conforme possível. Caso o objetivo seja apenas garantir a estabilidade de uma funcionalidade nova durante o período inicial de ganho de confiança, ela pode ser removida em seguida.

Permissioning Toggles: utilizada para liberar acesso a funcionalidades mediante perfil de usuário. Pode ser utilizada em releases Beta, onde apenas funcionários ou um grupo selecionado de usuários tenha acesso. Se a funcionalidade for acessível apenas para um subconjunto de usuários por tempo indeterminado, é possível que essa configuração seja permanente. Porém, caso seja apenas para uma estratégia de rollout mais controlada, ela deve ser removida assim que a funcionalidade esteja disponível para todo o universo de usuários da aplicação.

Exemplo simples de uso de feature toggles

Digamos que uma aplicação centralize os meios de pagamento disponíveis para adquirir um produto pela Livelo. Os meios de pagamento possíveis são Pix, cartão de crédito e pontos. O objetivo é que, mediante necessidades operacionais, qualquer meio de pagamento seja desativado por uma interface, abstraída pela aplicação em um config server. O código abaixo exemplifica uma possível abordagem para essa solução:

fun getPaymentOptions(): List<PaymentOption> {
var paymentOptions = mutableListOf<PaymentOption>()

if (configServer.isPixActive()) {
paymentOptions.add(PaymentOption("pix"))
}

if (configServer.isCreditCardActive()) {
paymentOptions.add(PaymentOption("credit_card"))
}

if (configServer.isPointsActive()) {
paymentOptions.add(PaymentOption("points"))
}

return paymentOptions
}

A flexibilidade proposta no código acima é interessante para que o sistema possa tolerar falhas com elegância, visto que a indisponibilidade de uma das opções não necessariamente impediria o participante de concluir a transação e obter o produto desejado. Porém, isso traz consigo uma complexidade inerente, visto que o sistema possui um dinamismo que precisa ser considerado nas etapas de design, desenvolvimento e testes. Podemos extrair as possíveis configurações do cenário acima, descritas na tabela abaixo:

+---------+---------+---------+
| Pix | Crédito | Pontos |
+---------+---------+---------+
| Inativo | Inativo | Inativo |
| Ativo | Inativo | Inativo |
| Inativo | Ativo | Inativo |
| Inativo | Inativo | Ativo |
| Ativo | Ativo | Inativo |
| Inativo | Ativo | Ativo |
| Ativo | Inativo | Ativo |
| Ativo | Ativo | Ativo |
+---------+---------+---------+

Sabendo dessas hipóteses, existem diversas perguntas que o time precisa fazer e direcionar:

  • Quem deve poder alterar as configurações? Qual será a plataforma indicada para isso? Como será feita a gestão de acessos? Será necessário dupla validação para manipular uma configuração do gênero?
  • Qual deve ser o comportamento do sistema caso nenhuma opção de pagamento esteja ativa em algum momento?
  • Qual o nível de atenção que deve ser dado caso algum meio de pagamento torne-se indisponível de forma inesperada? Quais alertas serão disparados e para quem? Qual deve ser o procedimento caso alguma forma de pagamento esteja indisponível?
  • Todas as combinações são consideradas nos testes automatizados da aplicação?
  • Como cada cenário reflete nos diferentes canais da aplicação?
  • Como a central de atendimento deverá fornecer suporte ao usuário dentro de cada cenário?

Vale considerar que em um cenário do tipo, as combinações são exponenciais. Ou seja, caso seja adicionado uma nova forma de pagamento, seguindo o mesmo modelo, a quantidade de combinações sobe de 8 para 16. Portanto, o design da solução precisa considerar a eficiência e testabilidade independente do volume de opções de pagamento disponibilizadas.

Além disso, o design do código de exemplo está rústico. Para construir uma solução que facilite a manutenibilidade da aplicação a longo prazo, é necessária uma abstração maior da regra de ativação ou inativação de meios de pagamento, utilizando inversão de controle e possivelmente o design pattern Strategy, evitando assim as condicionais no fluxo do código. Em um cenário real, também haveriam outros fatores, como possibilidades de pagamento, vantagens, prazo de entrega, etc., então o design precisa ser muito bem pensado ao envolver feature toggles nesse tipo de situação.

Considerações finais

  • Feature toggles podem ser implementadas de maneira simples, mas carregam consigo uma complexidade inerente que deve ser gerenciada. É necessário dedicar esforço no design da solução para não criar uma complexidade acidental em que o time não tem a visibilidade dos possíveis efeitos colaterais causados por diferentes configurações do sistema;
  • É necessário considerar também o ciclo de vida de cada feature toggle de acordo com o propósito. Por exemplo, onde cabível, é possível determinar uma data de expiração para forçar a remoção definitiva da configuração, preferencialmente com o auxílio de mecanismos automatizados na esteira de CI/CD. Isso evita com que o sistema fique cada vez mais complexo com o passar do tempo conforme novas funcionalidades são introduzidas;
  • Apesar dos desafios, é essencial a utilização e evolução na maturidade dos times que criam e gerenciam feature toggles para o melhor uso das mesmas. É uma forma de garantir a entrega contínua com risco drasticamente reduzido. O medo do deploy deve ser evitado para que as soluções sejam entregues em produção de forma segura, rápida e eficaz.

Referências

--

--

Jean Meira
Livelo
Writer for

Especialista em Engenharia de Sistemas na Livelo