Создание приложения, которое управляется свойствами, с помощью Spring Boot.

Этот пост считается второй частью моего предыдущего поста о некоторых очень часто используемых аннотациях во фреймворке Spring. В этом посте я хотел специально сосредоточиться на том, как мы можем создавать серверные службы с помощью Spring / Spring Boot и управлять определенными разделами приложения с помощью свойств, которые также могут быть предоставлены извне кодовой базы. Когда я говорю о свойствах, на очень высоком уровне я имею в виду способность изменять поведение приложения, просто используя свойства. Например, если бы мы создали серверную службу, которая позволяла бы выполнять вход в систему с использованием сторонних служб (например, вход в систему с использованием службы X или службы Y), и в любой момент времени была бы доступна только одна из них и мы хотим иметь возможность переключаться между двумя вариантами, используя свойства вне приложения.

С таким требованием становится ясно, что нам нужен способ извне приложения, чтобы диктовать приложению, какой вход в систему нужно включить. Мы достигаем этого, используя поддержку Spring для свойств (которые могут быть .properties или .yaml files). Эти свойства помещаются в каталог src/main/resources и начинаются со слова application, за которым следует дефис и имя профиля (если вы хотите узнать о нем больше, обратитесь к документации Spring по профилям).

Для этого поста я использую следующие версии инструментов и фреймворков:

  • Весенняя загрузка: 2.4.1
  • Котлин: 1.4.21
  • JDK: 15
  • Gradle: 6.7.1

Без лишних слов, вот как мы создаем приложение Spring Boot Kotlin, которое может условно переключаться между службами с использованием внешних свойств:

Шаг 1. Придумайте ключ и значение настраиваемого свойства

Первое, что мы делаем, - это придумываем имя настраиваемого свойства, чтобы указать службу входа в систему, которую мы хотим активировать. Мы можем назвать это authentication.service (убедитесь, что имя свойства уже занято. Чтобы убедиться в этом, обратитесь к Списку свойств Spring). Далее мы определяем возможные значения этого свойства. В нашем случае, если бы мы рассматривали только Service X / Service Y, мы могли бы определить допустимые значения как service-xили service-y. Поэтому, если бы мы хотели использовать Service X для входа в систему, мы бы добавили в наше приложение следующий файл .YAML (в этом примере я использую формат файла yaml):

authentication.service: service-x

И чтобы использовать Сервис Y для входа:

authentication.service: service-y

Шаг 2. Определите интерфейс для функциональности

Затем мы определяем интерфейс для функции входа в систему. Этот интерфейс может быть очень простым и содержать только один метод, который в основном выполняет функцию входа в систему. Обратите внимание, что все классы, которым требуется функция входа в систему, будут ссылаться только на AuthenticationService и НЕ конкретных реализаций службы X или Y.

interface AuthenticationService {
    fun login()
}

Шаг 3. Добавьте функцию входа в систему для конкретной службы

На этом этапе мы добавляем класс, позволяющий входить в систему с использованием службы X, и другой класс, который позволяет входить в систему с помощью службы Y. Обратите внимание, что оба этих класса будут реализовывать интерфейс, который мы определили на этапе 2.

class ServiceXAuthService: AuthenticationService {
    fun login() {    
        // Functionality to execute login using Service X 
    }
}
class ServiceYAuthService: AuthenticationService {
fun login() {    
        // Functionality to execute login using Service Y 
    }
}

Шаг 4. Считайте ключи службы аутентификации в объект

Чтобы использовать API или SDK из сторонней службы, нам нужно будет определить класс для хранения тех свойств, которые затем могут использоваться ServiceXAuthService и ServiceYAuthService для взаимодействия со службами X и Y соответственно.

Давайте также сделаем здесь еще пару предположений: Сервис X требует API-ключ для аутентификации, а Сервис Y требует API-ключа и API-секрета. Итак, мы создадим класс для хранения свойств сервиса X.

@ConstructorBinding
@ConfigurationProperties(prefix = "service-x")
data class ServiceXProperties(
    val apiKey: String
)

и один для хранения свойств сервиса Y:

@ConstructorBinding
@ConfigurationProperties(prefix = "service-y")
data class ServiceYProperties(
    val apiKey: String
    val apiSecret: String
)

Аннотации @ConstructorBinding и @ConfigurationProperties являются здесь ключевыми. @ConstructorBinding используется, чтобы указать, что свойства из файла свойств или YAML должны быть прочитаны в этот объект с помощью его конструктора, а аннотация @ConfigurationProperties указывает префикс свойства, которое необходимо прочитать из YAML или файла свойств.

Чтобы свойства были подобраны и установлены для классов должным образом, добавьте следующую зависимость в раздел зависимостей в файле build.gradle.kts (дополнительную информацию см. В этом документе):

annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

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

class ServiceXAuthService(
    private val authProperties: ServiceXProperties
): AuthenticationService {
    fun login() {    
        // Functionality to execute login using Service X 
    }
}
class ServiceYAuthService(
    private val authProperties: ServiceYProperties
): AuthenticationService {
    fun login() {    
        // Functionality to execute login using Service Y 
    }
}

Шаг 5: предоставление значений свойств

Теперь, когда у нас есть все классы, мы предоставляем значения для этих свойств в application.yaml (или application.properties файле. Я собираюсь использовать yaml файлы в качестве примеров здесь).

Если бы мы хотели использовать службу входа в Service X:

authentication.service: service-x
service-x:
  apiKey: X-Key
  apiSecret: X-Secret

Вместо этого, если бы мы хотели использовать Сервис Y в качестве поставщика услуг входа в систему, файл application.yaml будет выглядеть примерно так:

authentication.service: service-y
service-y.apiKey: Y-Key

Шаг 6: магия настройки

Это суть этого поста. После того, как весь код для этой функциональности был добавлен, мы теперь пишем класс конфигурации, который будет решать, какой bean (ServiceXAuthService или ServiceYAuthService) будет доступен для всех классов, которые требуют использования функциональности входа в систему. Набор аннотаций Spring Conditional позволяет нам прочитать свойство authentication.service и создать ServiceXAuthService, если значение равно service-x, или ServiceYAuthService, если значение равно service-y.

// For Service X Auth Service
@Configuration
@EnableConfigurationProperties(ServiceXProperties::class)
@ConditionalOnProperty(
    prefix = "authentication", 
    name = ["service"], 
    havingValue = "service-x"
)
class ServiceXAuthConfiguration(
    private val serviceXProperties: ServiceXProperties
) {
@Bean
   fun xAuthService(): AuthenticationService =  ServiceXAuthService(serviceXProperties)
}

И для подключения к функции входа в службу Y

// For Service Y Auth Service
@Configuration
@EnableConfigurationProperties(ServiceYProperties::class)
@ConditionalOnProperty(
    prefix = "authentication", 
    name = ["service"], 
    havingValue = "service-y"
)
class ServiceYAuthConfiguration(
    private val serviceYProperties: ServiceYProperties
) {
@Bean
   fun yAuthService(): AuthenticationService =    ServiceYAuthService(serviceYProperties)
}

@EnableConfigurationProperties в сочетании с @Configuration позволяет создавать bean-компонент типа, указанного в @EnableConfigurationProperties (в данном случае это будет либо ServiceXProperties, либо ServiceYProperties).

Пользователи также могут пропустить класс конфигурации и напрямую поместить эти аннотации вместе с аннотацией @Component в классы ServiceXAuthService и ServiceYAuthService. Это вопрос личных предпочтений. Мне нравится использовать класс конфигурации по нескольким причинам:

  1. Совершенно очевидно, какие классы представлены в приложении как bean-компоненты, и эта информация хранится в центральном месте.
  2. Если есть дополнительные компоненты / классы, которые, возможно, требуются для определенных условий, например, если нам нужен другой класс, который будет служить служебным классом с именем ServiceXAuthLoginHelper, и нам это нужно только тогда, когда у нас authentication.service установлено значение service-x, мы могли бы создать два классы конфигурации один для службы X и другой для службы Y, а класс конфигурации специально для входа в службу X может содержать код для создания и предоставления обоих этих классов в качестве компонентов, а условная аннотация может применяться ко всему классу.
  3. Благодаря встроенной поддержке Spring для тестирования классов конфигурации очень легко выполнить модульное тестирование классов конфигурации с условной логикой, чтобы убедиться, что все работает должным образом. Этот пост в блоге очень четко объясняет, как мы можем писать тесты для классов конфигурации.

Существует несколько вариантов аннотации ConditionalX, предоставляемой Spring, которая может быть основана на bean-компонентах, классах и т. Д. Дополнительные сведения см. В Annotation Types Summary этой документации.

Шаг 7. Превращение свойства в переменную среды

К настоящему времени вы, вероятно, задаетесь вопросом, как мы можем переключаться между предпочтительными службами входа в систему без изменения кода. В конце концов, нам нужно изменить значение authentication.service, чтобы вся условная магия работала. Что ж, это, наверное, второй по важности шаг в этом посте. Мы делаем значение authentication.service переменной окружения, тем самым делая его вводимым извне приложения!

authentication.service: ${authentication.service.name}

Теперь мы можем переключаться между учетными записями служб X и Y, указав service-x или service-y в качестве значения для переменной среды authentication.service.name и предоставив свойства, которые могут понадобиться каждой из этих служб, и запустить приложение, чтобы увидеть, какая служба создана и доступна для внедрения зависимостей. . Компонент другой службы не будет создан!

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

Вывод

Я надеюсь, что вы нашли этот пост в блоге информативным о том, как добиться условного поведения приложения, управляемого свойствами, с помощью Spring Boot!