1. Обзор
В этом уроке мы рассмотрим, как библиотека Togglz может использоваться с приложением Spring Boot.
2. Переключение
Библиотека Togglz /обеспечивает реализацию шаблона Переключения функций|/. Этот шаблон относится к наличию механизма, который позволяет определить во время выполнения приложения, включена ли определенная функция или нет на основе переключателя.
Отключение функции во время выполнения может быть полезно в различных ситуациях, таких как работа над новой функцией, которая еще не завершена, желание разрешить доступ к функции только подмножеству пользователей или выполнение A/B-тестирования.
В следующих разделах мы создадим аспект, который перехватывает методы с аннотацией, предоставляющей имя объекта, и определим, следует ли продолжать выполнение методов в зависимости от того, включена ли функция или нет.
3. Зависимости Maven
Наряду с зависимостями загрузки Spring библиотека Toggle предоставляет банку стартера загрузки Spring:
org.springframework.boot spring-boot-starter-parent 2.4.0 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
Последние версии toggle-spring-boot-starter , toggle-spring-security , spring-boot-starter-web , spring-boot-starter-data-jpa , spring-boot-starter-test , h2 можно загрузить с Maven Central.
4. Переключение конфигурации
Библиотека toggle-spring-boot-starter содержит автоматическую настройку для создания необходимых компонентов, таких как FeatureManager . Единственный компонент, который нам нужно предоставить, – это поставщик функций bean.
Во-первых, давайте создадим перечисление, которое реализует интерфейс Feature и содержит список имен объектов:
public enum MyFeatures implements Feature { @Label("Employee Management Feature") EMPLOYEE_MANAGEMENT_FEATURE; public boolean isActive() { return FeatureContext.getFeatureManager().isActive(this); } }
Перечисление также определяет метод с именем is Active () , который проверяет, включена ли определенная функция.
Затем мы можем определить компонент типа Поставщик функций на основе перечисления в классе конфигурации загрузки Spring:
@Configuration public class ToggleConfiguration { @Bean public FeatureProvider featureProvider() { return new EnumBasedFeatureProvider(MyFeatures.class); } }
5. Создание аспекта
Затем мы создадим аспект, который перехватывает пользовательский Связанный объект аннотация и проверяет объект, указанный в параметре аннотации, чтобы определить, активен он или нет:
@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; } } }
Давайте также определим пользовательскую аннотацию, называемую Ассоциацией объектов , которая будет иметь значение() параметр типа MyFeatures enum:
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) public @interface FeatureAssociation { MyFeatures value(); }
Если функция активна, аспект продолжит выполнение метода; если нет, он зарегистрирует сообщение без запуска кода метода.
6. Активация функции
Функция в Togglz может быть активной или неактивной. Это поведение контролируется флагом enabled и, возможно, стратегией активации.
Чтобы установить флаг enabled в значение true, мы можем использовать аннотацию @EnabledByDefault в определении значения перечисления.
Библиотека Toggle также предоставляет различные стратегии активации, которые можно использовать для определения того, включена ли функция на основе определенного условия.
В нашем примере давайте используем стратегию SystemPropertyActivation для нашего EMPLOYEE_MANAGEMENT_FEATURE, которая оценивает состояние функции на основе значения системного свойства. Требуемое имя и значение свойства можно указать с помощью аннотации @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; //... }
Мы установили, что наша функция будет включена только в том случае, если сотрудник.свойство feature имеет значение true .
Другими типами стратегий активации, предоставляемых библиотекой Toggle , являются:
- UsernameActivationStrategy – позволяет функции быть активными для указанного списка пользователей
- UserRoleActivationStrategy – роль текущего пользователя используется для определения состояния функции
- ReleaseDateActivationStrategy – автоматически активирует функцию в определенную дату и время
- GradualActivationStrategy – включает функцию для определенного процента пользователей
- ScriptEngineActivationStrategy – позволяет использовать пользовательский сценарий, написанный на языке, поддерживаемом ScriptEngine JVM, для определения того, активна функция или нет
- ServerIpActivationStrategy – функция включена на основе IP-адресов сервера
7. Тестирование аспекта
7.1. Пример применения
Чтобы увидеть наш аспект в действии, давайте создадим простой пример, содержащий функцию управления сотрудниками организации.
По мере развития этой функции мы можем добавлять методы и классы, аннотированные нашей аннотацией @AssociatedFeature со значением EMPLOYEE_MANAGEMENT_FEATURE. Это гарантирует, что они будут доступны только в том случае, если функция активна.
Во-первых, давайте определим класс Employee entity и репозиторий на основе данных Spring:
@Entity public class Employee { @Id private long id; private double salary; // standard constructor, getters, setters }
public interface EmployeeRepository extends CrudRepository{ }
Далее, давайте добавим Employee Service с методом увеличения зарплаты сотрудника. Мы добавим аннотацию @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); } }
Метод будет вызван из конечной точки /increase , которую мы вызовем для тестирования:
@Controller public class SalaryController { @Autowired SalaryService salaryService; @PostMapping("/increaseSalary") @ResponseBody public void increaseSalary(@RequestParam long id) { salaryService.increaseSalary(id); } }
7.2. Тест JUnit
Во-первых, давайте добавим тест, в котором мы вызываем наше сопоставление ЗАПИСЕЙ после установки employee.функция свойство 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. Выводы
В этом уроке мы показали, как мы можем интегрировать библиотеку Toggle с Spring Boot с помощью аспекта.
Полный исходный код примера можно найти на GitHub .