Bota de Primavera e Aspecto Togglz
1. Visão geral
Neste tutorial, vamos dar uma olhada em como a bibliotecaTogglz pode ser usada com um aplicativo Spring Boot.
2. Togglz
OTogglz library fornece uma implementação do padrão de designFeature Toggles. Esse padrão refere-se a um mecanismo que permite determinar durante o tempo de execução de um aplicativo se um determinado recurso está ativado ou não com base em uma alternância.
Desabilitar um recurso em tempo de execução pode ser útil em várias situações, como trabalhar em um novo recurso ainda não concluído, desejando permitir o acesso a um recurso apenas a um subconjunto de usuários ou executando testes A / B.
Nas seções a seguir, criaremos um aspecto que intercepta métodos com uma anotação que fornece um nome de recurso e determinamos se você continuará executando os métodos, dependendo se o recurso está ativado ou não.
3. Dependências do Maven
Junto com as dependências do Spring Boot, a bibliotecaTogglz fornece um jar Spring Boot Starter:
org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE
org.togglz
togglz-spring-boot-starter
2.4.1
org.togglz
togglz-spring-security
2.4.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-test
com.h2database
h2
1.4.194
As versões mais recentes detogglz-spring-boot-starter,togglz-spring-security,spring-boot-starter-web,spring-boot-starter-data-jpa,spring-boot-starter-test,h2 podem ser baixadas do Maven Central.
4. Configuração Togglz
A bibliotecatogglz-spring-boot-starter contém configuração automática para criar os beans necessários, comoFeatureManager. O único bean que precisamos fornecer é o beanfeatureProvider.
Primeiro, vamos criar uma enumeração que implemente a interfaceFeature e contenha uma lista de nomes de recursos:
public enum MyFeatures implements Feature {
@Label("Employee Management Feature")
EMPLOYEE_MANAGEMENT_FEATURE;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
A enumeração também define um método chamadoisActive() que verifica se um determinado recurso está habilitado.
Então podemos definir um bean do tipoEnumBasedFeatureProvider em uma classe de configuração Spring Boot:
@Configuration
public class ToggleConfiguration {
@Bean
public FeatureProvider featureProvider() {
return new EnumBasedFeatureProvider(MyFeatures.class);
}
}
5. Criando o Aspecto
A seguir, criaremos um aspecto que intercepta uma anotaçãoAssociatedFeature personalizada e verifica o recurso fornecido no parâmetro de anotação para determinar se está ativo ou não:
@Aspect
@Component
public class FeaturesAspect {
private static final Logger LOG = Logger.getLogger(FeaturesAspect.class);
@Around(
"@within(featureAssociation) || @annotation(featureAssociation)"
)
public Object checkAspect(ProceedingJoinPoint joinPoint,
FeatureAssociation featureAssociation) throws Throwable {
if (featureAssociation.value().isActive()) {
return joinPoint.proceed();
} else {
LOG.info(
"Feature " + featureAssociation.value().name() + " is not enabled!");
return null;
}
}
}
Vamos também definir a anotação personalizada chamadaFeatureAssociation que terá um parâmetrovalue() do tipoMyFeatures enum:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface FeatureAssociation {
MyFeatures value();
}
Se o recurso estiver ativo, o aspecto continuará a execução do método; Caso contrário, ele registrará uma mensagem sem executar o código do método.
6. Ativação de recursos
Um recurso emTogglz pode estar ativo ou inativo. Este comportamento é controlado por um sinalizadorenabled e, opcionalmente, por uma estratégia de ativação.
Para definir o sinalizadorenabled como verdadeiro, podemos usar a anotação@EnabledByDefault na definição do valor enum.
A bibliotecaTogglz também fornece uma variedade de estratégias de ativação que podem ser usadas para determinar se um recurso está habilitado com base em uma determinada condição.
Em nosso exemplo, vamos usarSystemPropertyActivationStrategy para nosso EMPLOYEE_MANAGEMENT_FEATURE, que avalia o estado do recurso com base no valor de uma propriedade do sistema. O nome e o valor da propriedade necessários podem ser especificados usando a anotação@ActivationParameter:
public enum MyFeatures implements Feature {
@Label("Employee Management Feature")
@EnabledByDefault
@DefaultActivationStrategy(id = SystemPropertyActivationStrategy.ID,
parameters = {
@ActivationParameter(
name = SystemPropertyActivationStrategy.PARAM_PROPERTY_NAME,
value = "employee.feature"),
@ActivationParameter(
name = SystemPropertyActivationStrategy.PARAM_PROPERTY_VALUE,
value = "true") })
EMPLOYEE_MANAGEMENT_FEATURE;
//...
}
Definimos nosso recurso para ser ativado apenas se a propriedadeemployee.feature tiver o valortrue.
Outros tipos de estratégias de ativação fornecidas pela bibliotecaTogglz são:
-
UsernameActivationStrategy - permite que o recurso esteja ativo para uma lista especificada de usuários
-
UserRoleActivationStrategy - a função do usuário atual é usada para determinar o estado de um recurso
-
ReleaseDateActivationStrategy - ativa automaticamente um recurso em uma determinada data e hora
-
GradualActivationStrategy - ativa um recurso para uma porcentagem especificada de usuários
-
ScriptEngineActivationStrategy - permite o uso de um script personalizado escrito em uma linguagem suportada porScriptEngine da JVM para determinar se um recurso está ativo ou não
-
ServerIpActivationStrategy - um recurso é habilitado com base nos endereços IP do servidor
7. Testando o Aspecto
7.1. Exemplo de aplicação
Para ver nosso aspecto em ação, vamos criar um exemplo simples que contém um recurso para gerenciar os funcionários de uma organização.
Como esse recurso será desenvolvido, podemos adicionar métodos e classes anotados com nossa anotação@AssociatedFeature com um valor de EMPLOYEE_MANAGEMENT_FEATURE. Isso garante que eles estarão acessíveis apenas se o recurso estiver ativo.
Primeiro, vamos definir uma classe de entidadeEmployee e um repositório com base em Spring Data:
@Entity
public class Employee {
@Id
private long id;
private double salary;
// standard constructor, getters, setters
}
public interface EmployeeRepository
extends CrudRepository{ }
A seguir, vamos adicionar umEmployeeService com um método para aumentar o salário de um funcionário. Vamos adicionar a anotação@AssociatedFeature ao método com um parâmetro deEMPLOYEE_MANAGEMENT_FEATURE:
@Service
public class SalaryService {
@Autowired
EmployeeRepository employeeRepository;
@FeatureAssociation(value = MyFeatures.EMPLOYEE_MANAGEMENT_FEATURE)
public void increaseSalary(long id) {
Employee employee = employeeRepository.findById(id).orElse(null);
employee.setSalary(employee.getSalary() +
employee.getSalary() * 0.1);
employeeRepository.save(employee);
}
}
O método será chamado de um endpoint/increaseSalary que chamaremos para teste:
@Controller
public class SalaryController {
@Autowired
SalaryService salaryService;
@PostMapping("/increaseSalary")
@ResponseBody
public void increaseSalary(@RequestParam long id) {
salaryService.increaseSalary(id);
}
}
7.2. Teste JUnit
Primeiro, vamos adicionar um teste no qual chamamos nosso mapeamento POST depois de definir a propriedadeemployee.feature parafalse. Nesse caso, o recurso não deve estar ativo e o valor do salário do funcionário não deve mudar:
@Test
public void givenFeaturePropertyFalse_whenIncreaseSalary_thenNoIncrease()
throws Exception {
Employee emp = new Employee(1, 2000);
employeeRepository.save(emp);
System.setProperty("employee.feature", "false");
mockMvc.perform(post("/increaseSalary")
.param("id", emp.getId() + ""))
.andExpect(status().is(200));
emp = employeeRepository.findOne(1L);
assertEquals("salary incorrect", 2000, emp.getSalary(), 0.5);
}
A seguir, vamos adicionar um teste onde realizamos a chamada depois de definir a propriedade paratrue. Nesse caso, o valor do salário deve ser aumentado:
@Test
public void givenFeaturePropertyTrue_whenIncreaseSalary_thenIncrease()
throws Exception {
Employee emp = new Employee(1, 2000);
employeeRepository.save(emp);
System.setProperty("employee.feature", "true");
mockMvc.perform(post("/increaseSalary")
.param("id", emp.getId() + ""))
.andExpect(status().is(200));
emp = employeeRepository.findById(1L).orElse(null);
assertEquals("salary incorrect", 2200, emp.getSalary(), 0.5);
}
8. Conclusões
Neste tutorial, mostramos como podemos integrar a bibliotecaTogglz com Spring Boot usando um aspecto.
O código-fonte completo do exemplo pode ser encontradoover on GitHub.