1. Обзор

В этом руководстве мы рассмотрим Flips, библиотеку, которая реализует флаги функций в виде мощных аннотаций для приложений Spring Core, Spring MVC и Spring Boot.

Флаги функций (или переключатели) — это шаблон для быстрой и безопасной доставки новых функций. Эти переключатели позволяют нам изменять поведение приложения без изменения или развертывания нового кода. В блоге Мартина Фаулера есть очень информативная статья о флагах функций здесь.

2. Зависимость от Maven

Прежде чем мы начнем, нам нужно добавить библиотеку Flips в наш pom.xml:

Maven Central имеет последнюю версию библиотеки, а проект Github находится здесь. .

<dependency>
    <groupId>com.github.feature-flip</groupId>
    <artifactId>flips-core</artifactId>
    <version>1.0.1</version>
</dependency>

Конечно, нам также нужно включить Spring:

Поскольку Flips еще не совместим с Spring версии 5.x, мы собираемся использовать последнюю версию Spring Boot в версии 4. х ветвь.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.10.RELEASE</version>
</dependency>

3. Простой сервис REST для Flips

Давайте составим простой проект Spring Boot для добавления и переключения новых функций и флагов.

Наше REST-приложение будет предоставлять доступ к ресурсам Foo:

Мы просто создадим сервис, который поддерживает список Foos:

public class Foo {
    private String name;
    private int id;
}

Мы будем обращаться к дополнительным методам сервиса по мере продвижения , но этого фрагмента должно быть достаточно, чтобы проиллюстрировать, что FlipService делает в системе.

@Service
public class FlipService {

    private List<Foo> foos;

    public List<Foo> getAllFoos() {
        return foos;
    }

    public Foo getNewFoo() {
        return new Foo("New Foo!", 99);
    }
}

И, конечно же, нам нужно создать контроллер:

4. Управление функциями на основе конфигурации

@RestController
public class FlipController {

    private FlipService flipService;

    // constructors

    @GetMapping("/foos")
    public List<Foo> getAllFoos() {
        return flipService.getAllFoos();
    }
}

Самое простое использование Flips — это включение или отключение функции на основе конфигурации. У Flips есть несколько аннотаций для этого.

4.1. Environment Property

Давайте представим, что мы добавили новую возможность в FlipService; получение Foos по их идентификатору.

Давайте добавим новый запрос в контроллер:

@FlipOnEnvironmentProperty контролирует, доступен ли этот API.

@GetMapping("/foos/{id}")
@FlipOnEnvironmentProperty(
  property = "feature.foo.by.id", 
  expectedValue = "Y")
public Foo getFooById(@PathVariable int id) {
    return flipService.getFooById(id)
      .orElse(new Foo("Not Found", -1));
}

Проще говоря, когда feature.foo.by.id равен Y, мы можем делать запросы по Id. Если это не так (или вообще не определено), Flips отключит метод API.

Если функция не включена, Flips выдаст исключение FeatureNotEnabledException, а Spring вернет клиенту REST сообщение «Не реализовано».

Когда мы вызываем API со свойством, установленным на N, мы видим следующее:

Как и ожидалось, Spring перехватывает FeatureNotEnabledException и возвращает статус 501 клиенту.

Status = 501
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {
    "errorMessage": "Feature not enabled, identified by method 
      public com.baeldung.flips.model.Foo
      com.baeldung.flips.controller.FlipController.getFooById(int)",
    "className":"com.baeldung.flips.controller.FlipController",
    "featureName":"getFooById"
}

4.2. Активный профиль

Spring уже давно дает нам возможность сопоставлять bean-компоненты с различными профилями, такими как dev, test или prod. Расширение этой возможности для сопоставления флагов функций с активным профилем имеет интуитивно понятный смысл.

Давайте посмотрим, как функции включаются или отключаются в зависимости от активного профиля Spring:

Аннотация @FlipOnProfiles принимает список имен профилей. Если активный профиль есть в списке, API доступен.

@RequestMapping(value = "/foos", method = RequestMethod.GET)
@FlipOnProfiles(activeProfiles = "dev")
public List getAllFoos() {
    return flipService.getAllFoos();
}

4.3. Выражения Spring

Язык выражений Spring (SpEL) — это мощный механизм для управления средой выполнения. У Flips есть способ переключать функции.

@FlipOnSpringExpression переключает метод, основанный на выражении SpEL, который возвращает логическое значение.

Давайте используем простое выражение для управления новой функцией:

4.4. Disable

@FlipOnSpringExpression(expression = "(2 + 2) == 4")
@GetMapping("/foo/new")
public Foo getNewFoo() {
    return flipService.getNewFoo();
}

Чтобы полностью отключить функцию, используйте @FlipOff:

В этом примере функция getFirstFoo() полностью недоступна.

@GetMapping("/foo/first")
@FlipOff
public Foo getFirstFoo() {
    return flipService.getLastFoo();
}

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

5. Управление функциями с помощью даты/времени

Flips может переключать функции в зависимости от даты/времени или дня недели. Привязка доступности новой функции к дню или дате имеет очевидные преимущества.

5.1. Дата и время

@FlipOnDateTime принимает имя свойства, отформатированное в формате ISO 8601.

Итак, давайте установим свойство, указывающее новую функцию, которая будет активна 1 марта:

Затем мы напишем API для получения первого Foo:

first.active.after=2018-03-01T00:00:00Z

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

@GetMapping("/foo/first")
@FlipOnDateTime(cutoffDateTimeProperty = "first.active.after")
public Foo getFirstFoo() {
    return flipService.getLastFoo();
}

5.2. День недели

«Библиотека предоставляет @FlipOnDaysOfWeek, который полезен для таких операций, как A/B-тестирование:

getFooByNewId() доступен только по понедельникам и средам.

@GetMapping("/foo/{id}")
@FlipOnDaysOfWeek(daysOfWeek={DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY})
public Foo getFooByNewId(@PathVariable int id) {
    return flipService.getFooById(id).orElse(new Foo("Not Found", -1));
}

6. Замена компонента

Включение и выключение методов полезно, но мы можем захотеть ввести новое поведение через новые объекты. @FlipBean указывает Flips вызывать метод в новом bean-компоненте.

Аннотация Flips может работать с любым Spring @Component. Пока что мы изменили только наш @RestController, давайте попробуем изменить нашу службу.

Мы создадим новый сервис с поведением, отличным от FlipService:

Мы заменим старый сервис getNewFoo() новой версией:

@Service
public class NewFlipService {
    public Foo getNewFoo() {
        return new Foo("Shiny New Foo!", 100);
    }
}

Flips будет направлять вызовы getNewThing() в Ньюфлипсервис. @FlipBean — еще один переключатель, наиболее полезный в сочетании с другими. Давайте посмотрим на это сейчас.

@FlipBean(with = NewFlipService.class)
public Foo getNewFoo() {
    return new Foo("New Foo!", 99);
}

7. Объединение переключателей

Мы объединяем переключатели, указывая более одного. Flips оценивает их последовательно с неявной логикой «И». Поэтому все они должны быть истинными, чтобы включить эту функцию.

Давайте объединим два наших предыдущих примера:

Мы использовали новый сервис configurable.

@FlipBean(
  with = NewFlipService.class)
@FlipOnEnvironmentProperty(
  property = "feature.foo.by.id", 
  expectedValue = "Y")
public Foo getNewFoo() {
    return new Foo("New Foo!", 99);
}

8. Заключение

В этом кратком руководстве мы создали простой сервис Spring Boot и включали и выключали API с помощью аннотаций Flips. Мы увидели, как функции переключаются с помощью информации о конфигурации и даты/времени, а также как функции можно переключать путем замены bean-компонентов во время выполнения.

Образцы кода, как всегда, можно найти на GitHub.

«