Sinalizadores de recursos com mola
1. Visão geral
Neste artigo, definiremos brevemente os sinalizadores de recursos e proporemos uma abordagem opinativa e pragmática para implementá-los em aplicativos Spring Boot. Em seguida, examinaremos iterações mais sofisticadas, aproveitando os diferentes recursos do Spring Boot.
Discutiremos vários cenários que podem exigir a sinalização de recursos e falaremos sobre as possíveis soluções. Faremos isso usando um aplicativo de exemplo do Bitcoin Miner.
2. Sinalizadores de recursos
Sinalizadores de recurso - às vezes chamados de alternância de recursos - são um mecanismo que nos permite habilitar ou desabilitar funcionalidades específicas de nosso aplicativo sem ter que modificar o código ou, idealmente, reimplantar nosso aplicativo.
Dependendo da dinâmica exigida por um determinado sinalizador de recurso, talvez seja necessário configurá-los globalmente, por instância do aplicativo ou mais granularmente - talvez por usuário ou solicitação.
Tal como acontece com muitas situações em Engenharia de Software, é importante tentar usar a abordagem mais direta que lida com o problema em questão, sem adicionar complexidade desnecessária.
Os sinalizadores de recursos são uma ferramenta poderosa que, quando usada com sabedoria, pode trazer confiabilidade e estabilidade ao nosso sistema. No entanto, quando são mal utilizados ou mal mantidos, podem rapidamente se tornar fontes de complexidade e dores de cabeça.
Existem muitos cenários em que os sinalizadores de recursos podem ser úteis:
Desenvolvimento baseado em tronco e recursos não triviais
No desenvolvimento baseado em tronco, principalmente quando queremos continuar nos integrando com frequência, podemos não estar prontos para lançar uma certa peça de funcionalidade. Os sinalizadores de recursos podem ser úteis para nos permitir continuar liberando sem disponibilizar nossas alterações até a conclusão.
Configuração específica do ambiente
Podemos nos encontrar exigindo certas funcionalidades para redefinir nosso banco de dados para um ambiente de teste E2E.
Como alternativa, podemos precisar usar uma configuração de segurança diferente para ambientes de não produção daquela usada no ambiente de produção.
Portanto, poderíamos aproveitar os sinalizadores de recursos para alternar a configuração correta no ambiente certo.
A/B testing
Liberar várias soluções para o mesmo problema e medir o impacto é uma técnica atraente que poderíamos implementar usando sinalizadores de recursos.
Liberação de canário
Ao implantar novos recursos, podemos decidir fazê-lo gradualmente, começando com um pequeno grupo de usuários e expandindo sua adoção à medida que validamos a correção de seu comportamento. Os sinalizadores de recursos nos permitem alcançar isso.
Nas seções a seguir, tentaremos fornecer uma abordagem prática para lidar com os cenários mencionados acima.
Vamos dividir as diferentes estratégias para sinalizar, começando com o cenário mais simples para, em seguida, passar para uma configuração mais granular e complexa.
3. Sinalizadores de recurso de nível de aplicativo
Se precisarmos abordar qualquer um dos dois primeiros casos de uso, os sinalizadores de recursos no nível do aplicativo são uma maneira simples de fazer as coisas funcionarem.
Um sinalizador de recurso simples normalmente envolve uma propriedade e alguma configuração com base no valor dessa propriedade.
3.1. Sinalizadores de recurso usando perfis Spring
Na primavera podemostake advantage of profiles. Conveniently, profiles enable us to configure certain beans selectively. With a few constructs around them, we can quickly create a simple and elegant solution for application-level feature flags.
Vamos fingir que estamos construindo um sistema de mineração BitCoin. Nosso software já está em produção e temos a tarefa de criar um algoritmo de mineração experimental e aprimorado.
Em nossoJavaConfig, podemos criar o perfil de nossos componentes:
@Configuration
public class ProfiledMiningConfig {
@Bean
@Profile("!experimental-miner")
public BitcoinMiner defaultMiner() {
return new DefaultBitcoinMiner();
}
@Bean
@Profile("experimental-miner")
public BitcoinMiner experimentalMiner() {
return new ExperimentalBitcoinMiner();
}
}
Então,with the previous configuration, we simply need to include our profile to opt-in for our new functionality. Hátons of ways of configuring our app em geral eenabling profiles in particular. Da mesma forma, existemtesting utilities para tornar nossas vidas mais fáceis.
Contanto que nosso sistema seja simples o suficiente,we could then create an environment-based configuration to determine which features flags to apply and which ones to ignore.
Vamos imaginar que temos uma nova IU baseada em cartões em vez de tabelas, junto com o minerador experimental anterior.
Gostaríamos de habilitar os dois recursos em nosso ambiente de aceitação (UAT). Podemos criar um arquivoapplication-uat.yml:
spring:
profiles:
include: experimental-miner,ui-cards
# More config here
Com o arquivo anterior instalado, precisaríamos apenas habilitar o perfil UAT no ambiente UAT para obter o conjunto de recursos desejado.
Também é importante entender como tirar vantagem despring.profiles.include. Comparado comspring.profiles.active,, o primeiro nos permite incluir perfis de uma maneira aditiva.
Em nosso caso, queremos que o perfiluat também inclua experimental-mine e ui-cards.
3.2. Sinalizadores de recurso usando propriedades personalizadas
Os perfis são uma maneira excelente e simples de realizar o trabalho. No entanto, podemos exigir perfis para outros fins. Ou talvez, desejemos criar uma infraestrutura de sinalizador de recursos mais estruturada.
Para esses cenários, as propriedades customizadas podem ser uma opção desejável.
Let’s rewrite our previous example taking advantage of @ConditionalOnProperty and our namespace:
@Configuration
public class CustomPropsMiningConfig {
@Bean
@ConditionalOnProperty(
name = "features.miner.experimental",
matchIfMissing = true)
public BitcoinMiner defaultMiner() {
return new DefaultBitcoinMiner();
}
@Bean
@ConditionalOnProperty(
name = "features.miner.experimental")
public BitcoinMiner experimentalMiner() {
return new ExperimentalBitcoinMiner();
}
}
O exemplo anterior se baseia na configuração condicional do Spring Boot e configura um componente ou outro, dependendo se a propriedade está definida comotrue oufalse (ou totalmente omitida).
O resultado é muito semelhante ao do 3.1, mas agora temos o nosso espaço para nome. Ter nosso espaço para nome nos permite criar arquivos YAML / properties significativos:
#[...] Some Spring config
features:
miner:
experimental: true
ui:
cards: true
#[...] Other feature flags
Além disso, esta nova configuração nos permite prefixar nossos sinalizadores de recurso - em nosso caso, usando o prefixofeatures.
Pode parecer um pequeno detalhe, mas à medida que nosso aplicativo cresce e a complexidade aumenta, essa iteração simples nos ajuda a manter nossos sinalizadores de recursos sob controle.
Vamos falar sobre outros benefícios dessa abordagem.
3.3. Usando @ConfigurationProperties
Assim que obtivermos um conjunto prefixado de propriedades, podemos criar umPOJO decorated with @ConfigurationProperties para obter um identificador programático em nosso código.
Seguindo nosso exemplo contínuo:
@Component
@ConfigurationProperties(prefix = "features")
public class ConfigProperties {
private MinerProperties miner;
private UIProperties ui;
// standard getters and setters
public static class MinerProperties {
private boolean experimental;
// standard getters and setters
}
public static class UIProperties {
private boolean cards;
// standard getters and setters
}
}
Ao colocar o estado dos nossos sinalizadores de recursos em uma unidade coesa, abrimos novas possibilidades, permitindo-nos expor facilmente essas informações para outras partes do nosso sistema, como a IU ou para sistemas downstream.
3.4. Configuração de recurso de exposição
Nosso sistema de mineração de Bitcoin recebeu uma atualização da interface do usuário que ainda não está totalmente pronta. Por esse motivo, decidimos sinalizá-lo. Podemos ter um aplicativo de página única usando React, Angular ou Vue.
Independentemente da tecnologia,we need to know what features are enabled so that we can render our page accordingly.
Vamos criar um endpoint simples para servir nossa configuração para que nossa IU possa consultar o back-end quando necessário:
@RestController
public class FeaturesConfigController {
private ConfigProperties properties;
// constructor
@GetMapping("/feature-flags")
public ConfigProperties getProperties() {
return properties;
}
}
Pode haver maneiras mais sofisticadas de fornecer essas informações, comocreating custom actuator endpoints. Porém, para fins deste guia, um ponto final do controlador parece uma solução suficientemente boa.
3.5. Mantendo o acampamento limpo
Embora possa parecer óbvio, uma vez que implementamos nossos sinalizadores de recursos com cuidado, é igualmente importante manter a disciplina ao se livrar deles quando não forem mais necessários.
Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. Isso significa que precisaremos ter certeza de que nossaConfigProperties, nossa configuração Java e nossos arquivosYAML estejam limpos e atualizados.
4. Sinalizadores de recursos mais granulares
Às vezes, nos encontramos em cenários mais complexos. Para testes A / B ou versões canário, nossa abordagem anterior simplesmente não é suficiente.
Para obter sinalizadores de recursos em um nível mais granular, talvez seja necessário criar nossa solução. Isso pode envolver a personalização de nossa entidade de usuário para incluir informações específicas de recursos ou talvez estender nossa estrutura da web.
No entanto, poluir nossos usuários com sinalizadores de recursos pode não ser uma idéia atraente para todos, e existem outras soluções.
Como alternativa, podemos aproveitar as vantagens de algumas ferramentas integradassuch as Togglz. Esta ferramenta adiciona alguma complexidade, mas oferece uma boa solução out-of-the-box eprovides first-class integration with Spring Boot.
Togglz suporta diferentesactivation strategies:
-
Username: Sinalizadores associados a usuários específicos
-
Gradual rollout: Sinalizadores habilitados para uma porcentagem da base de usuários. Isso é útil para versões do Canary, por exemplo, quando queremos validar o comportamento de nossos recursos
-
Release date: Podemos agendar sinalizadores para serem ativados em uma determinada data e hora. Isso pode ser útil para o lançamento de um produto, uma liberação coordenada ou ofertas e descontos
-
Client IP: Recursos sinalizados com base em IPs de clientes. Isso pode ser útil ao aplicar a configuração específica a clientes específicos, pois eles têm IPs estáticos
-
Server IP: Nesse caso, o IP do servidor é usado para determinar se um recurso deve ser habilitado ou não. Isso também pode ser útil para lançamentos de canários, com uma abordagem ligeiramente diferente da implantação gradual - como quando queremos avaliar o impacto no desempenho em nossas instâncias
-
ScriptEngine: Podemos habilitar sinalizadores de recurso com base emarbitrary scripts. Esta é sem dúvida a opção mais flexível
-
System Properties: Podemos definir certas propriedades do sistema para determinar o estado de um sinalizador de recurso. Isso seria bastante semelhante ao que alcançamos com nossa abordagem mais direta
5. Sumário
Neste artigo, tivemos a oportunidade de falar sobre sinalizadores de recursos. Além disso, discutimos como o Spring pode nos ajudar a obter algumas dessas funcionalidades sem adicionar novas bibliotecas.
Começamos definindo como esse padrão pode nos ajudar em alguns casos de uso comuns.
Em seguida, criamos algumas soluções simples usando as ferramentas prontas para uso Spring e Spring Boot. Com isso, criamos uma construção de sinalização de recurso simples, porém poderosa.
Abaixo, comparamos algumas alternativas. Passando da solução mais simples e menos flexível para um padrão mais sofisticado, embora mais complexo.
Por fim, fornecemos brevemente algumas diretrizes para criar soluções mais robustas. Isso é útil quando precisamos de um maior grau de granularidade.