Spring Boot и Togglz Aspect

Spring Boot и Togglz Aspect

1. обзор

В этом руководстве мы рассмотрим, как библиотекуTogglz можно использовать с приложением Spring Boot.

2. Togglz

Togglz library обеспечивает реализацию шаблона проектированияFeature Toggles. Этот шаблон относится к наличию механизма, который позволяет определять во время выполнения приложения, включена ли определенная функция на основе переключателя.

Отключение функции во время выполнения может быть полезно в различных ситуациях, таких как работа над новой функцией, которая еще не завершена, желание разрешить доступ к функции только подгруппе пользователей или проведение A / B-тестирования.

В следующих разделах мы создадим аспект, который перехватывает методы с аннотацией, предоставляющей имя функции, и определим, продолжать ли выполнение методов в зависимости от того, включена эта функция или нет.

3. Maven Зависимости

Наряду с зависимостями Spring Boot, библиотекаTogglz предоставляет банку 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

Последние версииtogglz-spring-boot-starter,togglz-spring-security,spring-boot-starter-web,spring-boot-starter-data-jpa,spring-boot-starter-test,h2 можно загрузить с Maven Central.

4. Конфигурация Togglz

Библиотекаtogglz-spring-boot-starter содержит автоконфигурацию для создания необходимых bean-компонентов, таких какFeatureManager. Единственный компонент, который нам нужно предоставить, - это компонентfeatureProvider.

Во-первых, давайте создадим перечисление, реализующее интерфейсFeature и содержащее список имен функций:

public enum MyFeatures implements Feature {

    @Label("Employee Management Feature")
    EMPLOYEE_MANAGEMENT_FEATURE;

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

Перечисление также определяет метод под названиемisActive(), который проверяет, включена ли определенная функция.

Затем мы можем определить bean-компонент типаEnumBasedFeatureProvider в классе конфигурации Spring Boot:

@Configuration
public class ToggleConfiguration {

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

5. Создание аспекта

Затем мы создадим аспект, который перехватывает пользовательскую аннотациюAssociatedFeature и проверяет функцию, указанную в параметре аннотации, чтобы определить, активна она или нет:

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

Давайте также определим пользовательскую аннотацию с именемFeatureAssociation, которая будет иметь параметрvalue() типаMyFeatures enum:

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

Если функция активна, аспект продолжит выполнение метода; если нет, то сообщение будет записано без запуска кода метода.

6. Активация функций

Функция вTogglz может быть активной или неактивной. Это поведение контролируется флагомenabled и, необязательно, стратегией активации.

Чтобы установить флагenabled в значение true, мы можем использовать аннотацию@EnabledByDefault в определении значения перечисления.

БиблиотекаTogglz также предоставляет различные стратегии активации, которые можно использовать для определения того, включена ли функция в зависимости от определенного условия.

В нашем примере давайте использоватьSystemPropertyActivationStrategy для нашего EMPLOYEE_MANAGEMENT_FEATURE, который оценивает состояние функции на основе значения свойства System. Требуемые имя и значение свойства можно указать с помощью аннотации@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;
    //...
}

Мы включили нашу функцию, только если свойствоemployee.feature имеет значениеtrue.

Другие типы стратегий активации, предоставляемые библиотекойTogglz:

  • UsernameActivationStrategy - позволяет активировать функцию для указанного списка пользователей

  • UserRoleActivationStrategy - роль текущего пользователя используется для определения состояния функции.

  • ReleaseDateActivationStrategy - автоматически активирует функцию в определенную дату и время

  • GradualActivationStrategy - включает функцию для указанного процента пользователей

  • ScriptEngineActivationStrategy - позволяет использовать настраиваемый сценарий, написанный на языке, поддерживаемомScriptEngine JVM, чтобы определить, активна функция или нет

  • ServerIpActivationStrategy - функция включается в зависимости от IP-адресов сервера

7. Тестирование аспекта

7.1. Пример приложения

Чтобы увидеть наш аспект в действии, давайте создадим простой пример, содержащий функцию для управления сотрудниками организации.

По мере развития этой функции мы можем добавлять методы и классы, аннотированные нашей аннотацией@AssociatedFeature со значением EMPLOYEE_MANAGEMENT_FEATURE. Это гарантирует, что они будут доступны, только если функция активна.

Во-первых, давайте определим класс сущности и репозиторийEmployee на основе данных Spring:

@Entity
public class Employee {

    @Id
    private long id;
    private double salary;

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

Затем давайте добавимEmployeeService с методом увеличения зарплаты сотрудника. Мы добавим аннотацию@AssociatedFeature к методу с параметромEMPLOYEE_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);
    }
}

Метод будет вызываться из конечной точки/increaseSalary, которую мы будем вызывать для тестирования:

@Controller
public class SalaryController {

    @Autowired
    SalaryService salaryService;

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

7.2. JUnit Test

Во-первых, давайте добавим тест, в котором мы вызываем наше POST-отображение после установки для свойстваemployee.feature значенияfalse. В этом случае функция не должна быть активна и величина зарплаты сотрудника не должна меняться:

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

Затем давайте добавим тест, в котором мы выполняем вызов после установки свойства наtrue. В этом случае значение зарплаты должно быть увеличено:

@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. Выводы

В этом руководстве мы показали, как можно интегрировать библиотекуTogglz с Spring Boot с помощью аспекта.

Полный исходный код примера можно найти вover on GitHub.