Spring Boot et aspect Togglz

Botte de printemps et aspect Togglz

1. Vue d'ensemble

Dans ce didacticiel, nous allons examiner comment la bibliothèqueTogglz peut être utilisée avec une application Spring Boot.

2. Togglz

LeTogglz library fournit une implémentation du modèle de conceptionFeature Toggles. Ce modèle fait référence à un mécanisme permettant de déterminer, au moment de l'exécution d'une application, si une fonctionnalité donnée est activée ou non en fonction d'une bascule.

Désactiver une fonctionnalité au moment de l'exécution peut être utile dans diverses situations, telles que le travail sur une nouvelle fonctionnalité qui n'est pas encore complète, le souhait d'autoriser l'accès à une fonctionnalité uniquement à un sous-ensemble d'utilisateurs ou l'exécution de tests A / B.

Dans les sections suivantes, nous allons créer un aspect qui intercepte les méthodes avec une annotation fournissant un nom de fonctionnalité et déterminer s'il faut continuer à exécuter les méthodes en fonction de l'activation ou non de la fonctionnalité.

3. Dépendances Maven

Avec les dépendances de Spring Boot, la bibliothèqueTogglz fournit un 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

Les dernières versions detogglz-spring-boot-starter,togglz-spring-security,spring-boot-starter-web,spring-boot-starter-data-jpa,spring-boot-starter-test,h2 peuvent être téléchargées depuis Maven Central.

4. Configuration Togglz

La bibliothèquetogglz-spring-boot-starter contient une configuration automatique pour créer les beans nécessaires tels queFeatureManager. Le seul bean que nous devons fournir est le beanfeatureProvider.

Tout d'abord, créons une énumération qui implémente l'interfaceFeature et contient une liste de noms de fonctionnalités:

public enum MyFeatures implements Feature {

    @Label("Employee Management Feature")
    EMPLOYEE_MANAGEMENT_FEATURE;

    public boolean isActive() {
        return FeatureContext.getFeatureManager().isActive(this);
    }
}

L'énumération définit également une méthode appeléeisActive() qui vérifie si une certaine fonctionnalité est activée.

Ensuite, nous pouvons définir un bean de typeEnumBasedFeatureProvider dans une classe de configuration Spring Boot:

@Configuration
public class ToggleConfiguration {

    @Bean
    public FeatureProvider featureProvider() {
        return new EnumBasedFeatureProvider(MyFeatures.class);
    }
}

5. Créer l'aspect

Ensuite, nous allons créer un aspect qui intercepte une annotationAssociatedFeature personnalisée et vérifie la fonctionnalité fournie dans le paramètre d'annotation pour déterminer si elle est active ou non:

@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;
        }
    }
}

Définissons également l'annotation personnalisée appeléeFeatureAssociation qui aura un paramètrevalue() de typeMyFeatures enum:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface FeatureAssociation {
    MyFeatures value();
}

Si la fonctionnalité est active, l'aspect poursuivra l'exécution de la méthode. sinon, il enregistrera un message sans exécuter le code de la méthode.

6. Activation des fonctionnalités

Une fonction enTogglz peut être active ou inactive. Ce comportement est contrôlé par un indicateurenabled et éventuellement une stratégie d'activation.

Pour définir l'indicateurenabled sur true, nous pouvons utiliser l'annotation@EnabledByDefault sur la définition de la valeur enum.

La bibliothèqueTogglz fournit également une variété de stratégies d'activation qui peuvent être utilisées pour déterminer si une fonctionnalité est activée en fonction d'une certaine condition.

Dans notre exemple, utilisons lesSystemPropertyActivationStrategy pour notre EMPLOYEE_MANAGEMENT_FEATURE qui évalue l'état de la fonctionnalité en fonction de la valeur d'une propriété System. Le nom et la valeur de propriété requis peuvent être spécifiés à l'aide de l'annotation@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;
    //...
}

Nous avons défini notre fonction pour être activée uniquement si la propriétéemployee.feature a la valeurtrue.

Les autres types de stratégies d'activation fournis par la bibliothèqueTogglz sont:

  • UsernameActivationStrategy - permet à la fonctionnalité d'être active pour une liste d'utilisateurs spécifiée

  • UserRoleActivationStrategy - le rôle de l'utilisateur actuel est utilisé pour déterminer l'état d'une fonctionnalité

  • ReleaseDateActivationStrategy - active automatiquement une fonction à une certaine date et heure

  • GradualActivationStrategy - active une fonctionnalité pour un pourcentage spécifié d'utilisateurs

  • ScriptEngineActivationStrategy - permet l'utilisation d'un script personnalisé écrit dans un langage pris en charge par lesScriptEngine de la JVM pour déterminer si une fonctionnalité est active ou non

  • ServerIpActivationStrategy - une fonctionnalité est activée en fonction des adresses IP du serveur

7. Test de l'aspect

7.1. Exemple d'application

Pour voir notre aspect en action, créons un exemple simple contenant une fonctionnalité de gestion des employés d'une organisation.

Au fur et à mesure que cette fonctionnalité sera développée, nous pouvons ajouter des méthodes et des classes annotées avec notre annotation@AssociatedFeature avec une valeur de EMPLOYEE_MANAGEMENT_FEATURE. Cela garantit qu'ils ne seront accessibles que si la fonctionnalité est active.

Tout d'abord, définissons une classe d'entité et un référentielEmployee basés sur Spring Data:

@Entity
public class Employee {

    @Id
    private long id;
    private double salary;

    // standard constructor, getters, setters
}
public interface EmployeeRepository
  extends CrudRepository{ }

Ensuite, ajoutons unEmployeeService avec une méthode pour augmenter le salaire d'un employé. Nous ajouterons l'annotation@AssociatedFeature à la méthode avec un paramètre 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);
    }
}

La méthode sera appelée à partir d'un point de terminaison/increaseSalary que nous appellerons pour le test:

@Controller
public class SalaryController {

    @Autowired
    SalaryService salaryService;

    @PostMapping("/increaseSalary")
    @ResponseBody
    public void increaseSalary(@RequestParam long id) {
        salaryService.increaseSalary(id);
    }
}

7.2. Test JUnit

Tout d'abord, ajoutons un test dans lequel nous appelons notre mappage POST après avoir défini la propriétéemployee.feature surfalse. Dans ce cas, la fonctionnalité ne doit pas être active et la valeur du salaire de l’employé ne doit pas changer:

@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);
}

Ensuite, ajoutons un test où nous effectuons l'appel après avoir défini la propriété surtrue. Dans ce cas, la valeur du salaire devrait être augmentée:

@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. Conclusions

Dans ce didacticiel, nous avons montré comment intégrer la bibliothèqueTogglz à Spring Boot en utilisant un aspect.

Le code source complet de l'exemple peut être trouvéover on GitHub.