Kora облачно ориентированный серверный фреймворк написанный на Java для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный разработчиками Т-Банк / Тинькофф

Kora is a cloud-oriented server-side Java framework for writing Java / Kotlin applications with a focus on performance, efficiency and transparency made by T-Bank / Tinkoff developers

Перейти к содержанию

Конфигурация

Модуль конфигурации отвечает за отображение значений из файлов конфигурации на классы в Kora и их последующее использование для настройки приложения.

HOCON

Поддержка HOCON реализована с помощью Typesafe Config. HOCON — это формат конфиг-файлов, основанный на JSON. Формат менее строгий нежели JSON и обладает слегка другим синтаксисом.

services {
    foo {
      bar = "SomeValue" 
      baz = 10 
      propRequired = ${REQUIRED_ENV_VALUE} 
      propOptional = ${?OPTIONAL_ENV_VALUE} 
      propDefault = 10
      propDefault = ${?NON_DEFAULT_ENV_VALUE} 
      propReference = ${services.foo.bar}Other${services.foo.baz} 
      propArray = ["v1", "v2"] 
      propArrayAsString = "v1, v2" 
      propMap = { 
          "k1" = "v1"
          "k2" = "v2"
      }
      propObject = { 
          p1 = "v1"
          p2 = "v2"
      }
      propObjects = [ 
        {
          p1 = "v1"
          p2 = "v2"
        },
        {
          p1 = "v3"
          p2 = "v4"
        }
      ]
    }
}

Отображение конфигурации в коде:

@ConfigSource("services.foo")
public interface FooConfig {

    String bar();

    Integer baz();

    String propRequired();

    @Nullable
    String propOptional();

    Integer propDefault();

    String propReference();

    List<String> propArray();

    List<String> propArrayAsString();

    Map<String, String> propMap();

    @ConfigValueExtractor
    public interface ObjectConfig {

        String p1();

        String p2();
    }

    ObjectConfig propObject();

    List<ObjectConfig> propObjects();
}
@ConfigSource("services.foo")
interface FooConfig {

    fun bar(): String

    fun baz(): Int

    fun propRequired(): String

    fun propOptional(): String?

    fun propDefault(): Int

    fun propReference(): String

    fun propArray(): List<String>

    fun propArrayAsString(): List<String>

    fun propMap(): Map<String, String>

    @ConfigValueExtractor
    interface ObjectConfig {

        fun p1(): String

        fun p2(): String
    }

    fun propObject(): ObjectConfig

    fun propObjects(): List<ObjectConfig>
}

Подключение

Зависимость build.gradle:

implementation "ru.tinkoff.kora:config-hocon"

Модуль:

@KoraApp
public interface Application extends HoconConfigModule { }

Зависимость build.gradle.kts:

implementation("ru.tinkoff.kora:config-hocon")

Модуль:

@KoraApp
interface Application : HoconConfigModule

Файл

По умолчанию ожидаются файлы конфигурации reference.conf и application.conf

Во-первых, все файлы reference.conf объединяются, во-вторых, файл application.conf накладывается на неразрешенный файл reference.conf, результат вычисляется и проверяется что все значения переменных доступны.

Предполагается что конфигурация приложения находится в файле application.conf, а конфигурации библиотек в reference.conf.

Приоритет считывания application.conf файла конфигурации:

  • Использовать файл из config.resource если указан (файл из resources директории)
  • Использовать файл из config.file если указан (файл из файловой системы)
  • Использовать файл application.conf если имеется (файл из resources директории)
  • Используется пустой файл конфигурации если все указанное выше отсутствует

Пример указания конфига при запуске в терминале через java:

java -Dconfig.file=path/to/configFile application

Пример указания конфига в build.gradle:

run {
    jvmArgs += [
        "-Dconfig.file=path/to/configFile",
    ]
}

YAML

Поддержка YAML реализована с помощью SnakeYAML.

services:
  foo:
    bar: "SomeValue" 
    baz: 10 
    propRequired: ${REQUIRED_ENV_VALUE} 
    propOptional: ${?OPTIONAL_ENV_VALUE} 
    propDefault: ${?NON_DEFAULT_ENV_VALUE:10} 
    propReference: ${services.foo.bar}Other${services.foo.baz} 
    propArray: ["v1", "v2"] 
    propArrayAsString: "v1, v2" 
    propMap: 
      k1: "v1"
      k2: "v2"
    propObject: 
      p1: "v1"
      p2: "v2"
    propObjects: 
      - p1: "v1"
        p2: "v2"
      - p1: "v1"
        p2: "v2"

Отображение конфигурации в коде:

@ConfigSource("services.foo")
public interface FooConfig {

    String bar();

    Integer baz();

    String propRequired();

    @Nullable
    String propOptional();

    Integer propDefault();

    String propReference();

    List<String> propArray();

    List<String> propArrayAsString();

    Map<String, String> propMap();

    @ConfigValueExtractor
    public interface ObjectConfig {

        String p1();

        String p2();
    }

    ObjectConfig propObject();

    List<ObjectConfig> propObjects();
}
@ConfigSource("services.foo")
interface FooConfig {

    fun bar(): String

    fun baz(): Int

    fun propRequired(): String

    fun propOptional(): String?

    fun propDefault(): Int

    fun propReference(): String

    fun propArray(): List<String>

    fun propArrayAsString(): List<String>

    fun propMap(): Map<String, String>

    @ConfigValueExtractor
    interface ObjectConfig {

        fun p1(): String

        fun p2(): String
    }

    fun propObject(): ObjectConfig

    fun propObjects(): List<ObjectConfig>
}

Подключение

Зависимость build.gradle:

implementation "ru.tinkoff.kora:config-yaml"

Модуль:

@KoraApp
public interface Application extends YamlConfigModule { }

Зависимость build.gradle.kts:

implementation("ru.tinkoff.kora:config-yaml")

Модуль:

@KoraApp
interface Application : YamlConfigModule

Файл

По умолчанию ожидаются файлы конфигурации reference.yaml и application.yaml.

Во-первых, все файлы reference.yaml объединяются, во-вторых, файл application.yaml накладывается на неразрешенный файл reference.yaml, результат вычисляется и проверяется что все значения переменных доступны.

Предполагается что конфигурация приложения находится в файле application.yaml, а конфигурации библиотек в reference.yaml.

Приоритет считывания application.yaml файла конфигурации:

  • Использовать файл из config.resource если указан (файл из resources директории)
  • Использовать файл из config.file если указан (файл из файловой системы)
  • Использовать файл application.yaml если имеется (файл из resources директории)
  • Используется пустой файл конфигурации если все указанное выше отсутствует

Пример указания конфига при запуске в терминале через java:

java -Dconfig.file=path/to/configFile application

Пример указания конфига в build.gradle:

run {
    jvmArgs += [
        "-Dconfig.file=path/to/configFile",
    ]
}

Пользовательские конфигурации

Пользовательская конфигурация предоставляет собой отображение файла конфигурации на пользовательский интерфейс. Такой пользовательский интерфейс в последствии может быть внедрен как зависимость наравне с другими компонентами.

В приложении

Для создания пользовательских конфигураций следует использовать аннотацию @ConfigSource:

@ConfigSource("services.foo")
public interface FooServiceConfig {

    String bar();

    int baz();
}
@ConfigSource("services.foo")
interface FooServiceConfig {

    fun bar(): String

    fun baz(): Int
}

Этот пример кода добавит в контейнер экземпляр класса FooServiceConfig, который при создании будет ожидать конфигурацию следующего вида:

services {
  foo {
    bar = "SomeValue"
    baz = 10
  }
}
services:
  foo:
    bar: "SomeValue"
    baz: 10

После этого класс FooServiceConfig уже можно использовать как зависимость в других классах:

@Component
public final class FooService {

    private final FooServiceConfig config;

    public FooService(FooServiceConfig config) {
        this.config = config;
    }
}
@Component
class FooService(val config: FooServiceConfig)

В библиотеке

Для создания пользовательских конфигураций в рамках пользовательских библиотеках следует использовать аннотацию @ConfigValueExtractor которая создаст правила обработки файла конфигурации в экземпляр класса конфигурации.

Рассмотрим пример когда есть такой класс конфигурации:

@ConfigValueExtractor
public interface FooLibraryConfig {

    String bar();

    int baz();
}
@ConfigValueExtractor
interface FooLibraryConfig {

    fun bar(): String

    fun baz(): Int
}

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

public interface FooLibraryModule {

    default FooLibraryConfig config(Config config, ConfigValueExtractor<FooLibraryConfig> extractor) {
        return extractor.extract(config.get("library.foo"));
    }
}
interface FooLibraryModule {

    fun config(config: Config, extractor: ConfigValueExtractor<FooLibraryConfig>): FooLibraryConfig {
        return extractor.extract(config["library.foo"])!!
    }
}

Фабрика будет ожидать конфигурацию следующего вида:

library {
  foo {
    bar = "SomeValue"
    baz = 10
  }
}
library:
  foo:
    bar: "SomeValue"
    baz: 10

Затем подключив модуль FooLibraryModule в приложении, конфиг FooServiceConfig можно использовать как зависимость в других классах.

Обязательные значения

По умолчанию все значения объявленные в конфиге считаются обязательными (NotNull) и должны присутствовать в файле конфигурации.

Необязательные значения

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

Предлагается использовать аннотацию @Nullable над сигнатурой метода:

@ConfigSource("services.foo")
public interface FooServiceConfig {

    @Nullable
    String bar();

    int baz();
}

Предполагается использовать Kotlin Nullability синтаксис и помечать такой параметр как Nullable:

@ConfigSource("services.foo")
interface FooServiceConfig {

    fun bar(): String?

    fun baz(): Int
}

Значения по умолчанию

Если есть необходимость использовать задать в отображении значение по умолчанию, то можно воспользоваться default модификатором:

@ConfigSource("services.foo")
public interface FooServiceConfig {

    String bar();

    default int baz() {
        return 42;
    }
}
@ConfigSource("services.foo")
interface FooServiceConfig {

    fun bar(): String

    fun baz(): Int {
        return 42
    }
}

Внедрение конфигурации

Можно внедрять базовый класс ru.tinkoff.kora.config.common.Config который предоставляет из себя общую абстракцию над отображением файла конфигурации. Результирующие отображение конфигурации состоит из нескольких слоев которые представляют из себя:

  • Переменные окружения
  • Системные переменные
  • Файл конфигурации

Переменные окружения

В случае если требуется внедрить конфигурацию только переменных окружения, то для этого можно использовать аннотацию @Environment как тег для класса конфигурации:

@Component
public final class FooService {

    private final Config config;

    public FooService(@Environment Config config) {
        this.config = config;
    }
}
@Component
class FooService(@Environment val config: Config)

Системные переменные

В случае если требуется внедрить конфигурацию только системных переменных, то для этого можно использовать аннотацию @SystemProperties как тег для класса конфигурации:

@Component
public final class FooService {

    private final Config config;

    public FooService(@SystemProperties Config config) {
        this.config = config;
    }
}
@Component
class FooService(@SystemProperties val config: Config)

Файл конфигурации

В случае если требуется внедрить полную конфигурацию приложения которая состоит только из файла конфигурации, то для этого можно использовать аннотацию @ApplicationConfig как тег для класса конфигурации:

@Component
public final class FooService {

    private final Config config;

    public FooService(@ApplicationConfig Config config) {
        this.config = config;
    }
}
@Component
class FooService(@ApplicationConfig val config: Config)

Результирующая конфигурация

В случае если требуется внедрить полную конфигурацию приложения которая состоит из файла конфигурации, переменных окружения и системных переменных, то для этого требуется просто внедрить класс конфигурации без тега:

@Component
public final class FooService {

    private final Config config;

    public FooService(Config config) {
        this.config = config;
    }
}
@Component
class FooService(val config: Config)

Рекомендации

Совет

Мы не рекомендуем использовать напрямую ru.tinkoff.kora.config.common.Config как зависимость в компонентах, так как при обновлении конфигурации это повлечет обновление всех компонент графа которые используют его у себя, рекомендуется всегда создавать пользовательские конфигурации.

Наблюдатель

По умолчанию в Kora работает наблюдатель за файлом конфигурации который обновляет его содержимое, что влечет в случае изменения файла конфигурации обновление графа зависимостей для компонент которые затронули изменения.

Можно отключить наблюдатель с помощь:

  1. Переменной окружения KORA_CONFIG_WATCHER_ENABLED
  2. Системного свойства kora.config.watcher.enabled

Поддерживаемые типы

Экстракторы конфигурации предоставляют обширный список поддерживаемых типов, который охватывает большинство из того, что вам может понадобиться для указания в пользовательских конфигурациях, либо вы можете расширить поведение собственным ConfigValueExtractor<T> компонентом.

Список поддерживаемых типов
  • boolean / Boolean
  • short / Short
  • int / Integer
  • long / Long
  • double / Double
  • float / Float
  • double[]
  • String
  • BigInteger
  • BigDecimal
  • Period
  • Duration
  • Size
  • Properties
  • Pattern
  • UUID
  • Properties
  • LocalDate
  • LocalTime
  • LocalDateTime
  • OffsetTime
  • OffsetDateTime
  • Enum (любой пользовательский ENUM тип)
  • List<T> (где T любой из выше перечисленных типов)
  • Set<T> (где T любой из выше перечисленных типов)
  • Map<String, T> (где T любой из выше перечисленных типов)
  • Either<A, B> (где A и B любой из выше перечисленных типов)

Размер

Size - специальный тип который позволяет задавать размер байт в удобной человеку системе исчислений по стандарту IEEE 1541—2002 (двоичный), так и в стандарте СИ (десятичный).

Примеры значений:

  • 1Mb - 1 мегабайт (1.000.000 байт)
  • 1Mib - 1 мегабит (1.048.576 байт)
  • 1024b - 1024 байт
  • 1024 - 1024 байт

Если указано просто число без суффикса, то считается что указаны байты.