null

Еще один способ подключения common модуля для микросервисов

Введение

В статье тыцк был рассмотрен один из возможных способов подключения разделяемого между микросервисами common модуля при помощи ImportBeanDefinitionRegistrar Spring-а. Он обладает существенным недостатком: инструменты статического анализа кода не могут понимать такую “уличную магию”. Поэтому в данной статье рассмотрен другой способ решения этой же задачи при помощи классического Spring starter-а.

Состав common модуля

Для примера предположим, что в вашем common модуле находятся Spring-компоненты, решающие следующие задачи:

  1. Конфигурация CORS

  2. Логирование и трассировка

  3. Обработка ошибок и унификация формата тела ответа сервера при возникновении ошибок

  4. Feature flags

Функции 2 и 3, очевидно, будут востребованы в каждом разрабатываемом микросервисе, тогда как функции 1 и 4 - нет. То есть нам также нужна возможность конфигурации используемых функций common-модуля, чтобы не притаскивать в runtime микросервиса ненужные вещи.

Данные функции оформлены в виде отдельного Kotlin-модуля, среди зависимостей которого отсутствует Spring Boot, а также application-сервер. Common модуль в своих зависимостях объявляет только конкретные версии отдельных библиотек экосистемы Spring, необходимых для успешной компиляции разделяемых функций.

 

Немного теории

Напомним, что в составе Spring Boot есть подсистема автоконфигурации, пожалуй, одна из наиболее удобных разработок всего проекта Spring Framework. Она позволяет подключать стартеры, внутри которых содержится определение конфигурации различных подпроектов Spring-а “по умолчанию”. Если рассматривать внутреннее устройство стартеров, то для создания стартера необходимо:

  1. Разработать класс автоконфигурации и пометить его аннотацией @AutoConfiguration (строго говоря, допускается также и обычная @Configuration)

  2. Написать служебный файл META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports с указанием полного пути к классу автоконфигурации

При запуске Spring Boot приложения, будут найдены все файлы META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, доступные в classpath приложения (включая внутренности jar архивов зависимостей). После чего будут найдены классы автоконфигураций, указанные в упомянутых файлах. Далее классы автоконфигураций будут обработаны обычным образом, как и любые другие конфигурации.

Дополнительно к возможностям обычных конфигураций используются аннотации @ConditionalOn*, которые позволяют добавить в ApplicationContext бины опционально в зависимости от произвольных условий. Наиболее популярными условиями являются: по отсутствию заданного класса и по значению параметра конфигурации.

 

Реализация

Для нашего common модуля мы можем реализовать автоконфигурацию на основе следующего принципа. Внутри класса автоконфигурации расположим вложенные классы обычных конфигураций с аннотациями @ConditionalOnProperty, чтобы привязать включение того или иного модуля в ApplicationContext в зависимости от булевых переменных, задаваемых в конфигурации микросервисов.

@AutoConfiguration
@ConditionalOnProperty("gentleman-toolkit.enabled", havingValue = "true", matchIfMissing = false)
@EnableConfigurationProperties(value = [
    KafkaProperties::class,
    GentlemanToolkitProperties::class
])
class GentlemanToolkitAutoConfiguration {
    @ConditionalOnProperty("gentleman-toolkit.provide-cors-config", havingValue = "true", matchIfMissing = false)
    @Configuration
    class AllowedCorsConfigProvider {
        @Bean
        fun devAllowedCorsConfig(
            @Value("\${cors.allowed-origins}")
            allowedOrigins: Array<String>
        ): WebMvcConfigurer = AllowedCorsConfig(allowedOrigins)
    }

    @ConditionalOnProperty("gentleman-toolkit.provide-exception-handlers", havingValue = "true", matchIfMissing = false)
    @Configuration
    class ExceptionHandlerProvider {
        @Bean
        fun commonExceptionHandler(): CommonExceptionHandler = CommonExceptionHandler()

        @Bean
        fun defaultExceptionHandler(): DefaultExceptionHandler = DefaultExceptionHandler()
    }

    @ConditionalOnProperty("gentleman-toolkit.provide-logging-filter", havingValue = "true", matchIfMissing = false)
    @Configuration
    class LoggingFilterBeanProvider {
        @Bean
        @Order(1)
        fun loggingFilter(): LoggingFilter = LoggingFilter()
    }

    @ConditionalOnProperty("gentleman-toolkit.provide-feature-flags", havingValue = "true", matchIfMissing = false)
    @Configuration
    class FeatureFlagsProvider {
        @Bean
        @Profile("stage", "prod")
        fun gitlabFeatureFlagsManager(
            environment: Environment
        ): FeatureFlagsManager = GitLabFeatureFlagsManager(environment)

        @Bean
        @Profile("dev")
        fun stubFeatureFlagsManager(
            environment: Environment
        ): FeatureFlagsManager = StubFeatureFlagsManager(environment)
    }
}

Далее остается указать автоконфигурацию в служебном файле Spring-а и подключить сам проект common модуля к микросервису.

org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.newdex.services.common.GentlemanToolkitAutoConfiguration
Вперед