1. Обзор
В этом руководстве мы рассмотрим Flips, библиотеку, которая реализует флаги функций в виде мощных аннотаций для приложений Spring Core, Spring MVC и Spring Boot.
Флаги функций (или переключатели) — это шаблон для быстрой и безопасной доставки новых функций. Эти переключатели позволяют нам изменять поведение приложения без изменения или развертывания нового кода. В блоге Мартина Фаулера есть очень информативная статья о флагах функций здесь .
2. Зависимость от Maven
Прежде чем мы начнем, нам нужно добавить библиотеку Flips в наш pom.xml:
<dependency>
<groupId>com.github.feature-flip</groupId>
<artifactId>flips-core</artifactId>
<version>1.0.1</version>
</dependency>
У Maven Central есть последняя версия библиотеки , а проект Github здесь .
Конечно, нам также нужно включить Spring:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.10.RELEASE</version>
</dependency>
Поскольку Flips еще не совместим с Spring версии 5.x, мы собираемся использовать последнюю версию Spring Boot в ветке 4.x.
3. Простой REST-сервис для флипов
Давайте составим простой проект Spring Boot для добавления и переключения новых функций и флагов.
Наше REST-приложение предоставит доступ к ресурсам Foo :
public class Foo {
private String name;
private int id;
}
Мы просто создадим Service
, который поддерживает список Foos
:
@Service
public class FlipService {
private List<Foo> foos;
public List<Foo> getAllFoos() {
return foos;
}
public Foo getNewFoo() {
return new Foo("New Foo!", 99);
}
}
Мы обратимся к дополнительным методам службы по ходу дела, но этого фрагмента должно быть достаточно, чтобы проиллюстрировать, что FlipService
делает в системе.
И, конечно же, нам нужно создать контроллер:
@RestController
public class FlipController {
private FlipService flipService;
// constructors
@GetMapping("/foos")
public List<Foo> getAllFoos() {
return flipService.getAllFoos();
}
}
4. Функции управления на основе конфигурации
Самое простое использование Flips — это включение или отключение функции на основе конфигурации. У Flips есть несколько аннотаций для этого.
4.1. Свойство окружающей среды
Давайте представим, что мы добавили новую возможность в FlipService
; получение Foos
по их идентификатору.
Добавим новый запрос в контроллер:
@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));
}
@FlipOnEnvironmentProperty определяет, доступен
ли этот API.
Проще говоря, когда feature.foo.by.id
равен Y
, мы можем делать запросы по Id. Если это не так (или вообще не определено), Flips отключит метод API.
Если функция не включена, Flips выдаст FeatureNotEnabledException,
а Spring вернет клиенту REST сообщение «Не реализовано».
Когда мы вызываем API со свойством, установленным на N
, мы видим следующее:
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.foreach.flips.model.Foo
com.foreach.flips.controller.FlipController.getFooById(int)",
"className":"com.foreach.flips.controller.FlipController",
"featureName":"getFooById"
}
Как и ожидалось, Spring перехватывает FeatureNotEnabledException
и возвращает клиенту статус 501.
4.2. Активный профиль
Spring уже давно дает нам возможность сопоставлять bean-компоненты с различными профилями , такими как dev
, test
или prod
. Расширение этой возможности для сопоставления флагов функций с активным профилем имеет интуитивно понятный смысл.
Давайте посмотрим, как функции включаются или отключаются на основе активного профиля Spring :
@RequestMapping(value = "/foos", method = RequestMethod.GET)
@FlipOnProfiles(activeProfiles = "dev")
public List getAllFoos() {
return flipService.getAllFoos();
}
Аннотация @FlipOnProfiles
принимает список имен профилей. Если активный профиль есть в списке, API доступен.
4.3. Весенние выражения
Язык выражений Spring (SpEL) — это мощный механизм для управления средой выполнения. У Flips есть способ переключать функции.
@FlipOnSpringExpression
переключает метод, основанный на выражении SpEL, который возвращает логическое значение.
Давайте используем простое выражение для управления новой функцией:
@FlipOnSpringExpression(expression = "(2 + 2) == 4")
@GetMapping("/foo/new")
public Foo getNewFoo() {
return flipService.getNewFoo();
}
4.4. Запрещать
Чтобы полностью отключить функцию, используйте @FlipOff
:
@GetMapping("/foo/first")
@FlipOff
public Foo getFirstFoo() {
return flipService.getLastFoo();
}
В этом примере getFirstFoo()
полностью недоступен.
Как мы увидим ниже, мы можем комбинировать аннотации Flips, что позволяет использовать @FlipOff
для отключения функции на основе среды или других критериев.
5. Функции управления с датой/временем
Флипы могут переключать функцию в зависимости от даты/времени или дня недели. Привязка доступности новой функции к дню или дате имеет очевидные преимущества.
5.1. Дата и время
@FlipOnDateTime
принимает имя свойства, отформатированное в формате ISO 8601 .
Итак, давайте установим свойство, указывающее новую функцию, которая будет активна 1 марта:
first.active.after=2018-03-01T00:00:00Z
Затем мы напишем API для получения первого Foo:
@GetMapping("/foo/first")
@FlipOnDateTime(cutoffDateTimeProperty = "first.active.after")
public Foo getFirstFoo() {
return flipService.getLastFoo();
}
Flips проверит указанное свойство. Если свойство существует и указанная дата/время прошли, функция включена.
5.2. День недели
Библиотека предоставляет @FlipOnDaysOfWeek
, что полезно для таких операций, как A/B-тестирование:
@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));
}
getFooByNewId()
доступен только по понедельникам и средам.
6. Замените бин
Включение и выключение методов полезно, но мы можем захотеть ввести новое поведение через новые объекты. @FlipBean
указывает Flips вызывать метод в новом bean-компоненте.
Аннотация Flips может работать с любым компонентом Spring @Component.
Пока что мы изменили только наш @RestController
, давайте попробуем изменить наш Service.
Мы создадим новый сервис с поведением, отличным от FlipService
:
@Service
public class NewFlipService {
public Foo getNewFoo() {
return new Foo("Shiny New Foo!", 100);
}
}
Мы заменим getNewFoo()
старой службы новой версией:
@FlipBean(with = NewFlipService.class)
public Foo getNewFoo() {
return new Foo("New Foo!", 99);
}
Flips будет направлять вызовы getNewThing()
в NewFlipService. @FlipBean
— еще один переключатель, наиболее полезный в сочетании с другими. Давайте посмотрим на это сейчас.
7. Объединение переключателей
Мы объединяем переключатели, указывая более одного. Flips оценивает их последовательно с неявной логикой «И». Поэтому все они должны быть истинными, чтобы включить эту функцию.
Давайте объединим два наших предыдущих примера:
@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 .