Конфигурация
Конфигурация в ECOS делится на два основных уровня:
Системный уровень
Уровень администратора
Системный уровень
Данный уровень конфигурации необходим для настройки низкоуровневых параметров, которые не предполагается изменять в ходе работы приложеня (параметры подключение к БД, настройки для включения/отключения сервисов в приложении и др).
Конфигурация системного уровня настраивается средствами Spring Cloud - https://spring.io/projects/spring-cloud
Контейнер ecos-registry при старте загружает конфигурацию из указанных в настройках директорий и распространяет эту конфигурацию по всем приложениям.
Настройка ecos-registry через переменные среды:
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_TYPE=native
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_SEARCH_LOCATIONS=file:/central-config/
Путь во втором параметре указывает на место, откуда будет загружаться конфигурация
Уровень администратора
Конфигурация ECOS - это артефакт, который позволяет описывать некоторые параметры системы с возможностью удобно использовать их значения как в коде так и в интерфейсе для изменения параметров без перезагрузки сервера.
Расположение артефактов: resources/eapps/artifacts/app/config
Поддерживаемые форматы: yml, yaml, json
Ответственный за хранение и управление микросервис: ecos-apps
Можно использовать: в любом приложении
Доступ в интерфейсе есть у администраторов через инструменты: Левое меню → Инструменты → Управление системой → Конфигурация ECOS:
Путь до страницы с конфигурацией: /v2/admin?journalId=ecos-configs&type=JOURNAL
Примеры конфигураций:
---
id: active-theme
name:
ru: Активная тема
en: Active theme
value: ecos
---
id: custom-feedback-url
name:
ru: URL для обратной связи
en: URL for feedback
value: 'https://www.citeck.ru/feedback'
Чтобы воспользоваться этой конфигурацией в коде мы можем установить аннотацию @EcosConfig(“идентификатор_конфигурации”)
на поле или метод, куда нужно поместить значение конфигурации.
Любые изменения конфигурации в интерфейсе приведут к автоматическому обновлению значения поля или вызову метода, который был помечен аннотацией @EcosConfig
.
Пример:
// kotlin
@Component
class CustomComponent {
// Проставление через поле можно использовать
// если нам не важно отлавливать событие изменения
// Для полей можно использовать "var" и "lateinit var"
@EcosConfig("some-config-id")
private var configValue: String? = null
// Проставление через метод можно использовать
// если нам важно отлавливать событие изменения
@EcosConfig("some-config-id")
private fun setConfig(value: String) {
println("New value: $value")
}
}
// java
@Component
public class CustomComponent {
// Проставление через поле можно использовать
// если нам не важно отлавливать событие изменения
@EcosConfig("some-config-id")
private String configValue;
// Проставление через метод можно использовать
// если нам важно отлавливать событие изменения
@EcosConfig("some-config-id")
private void setConfig(String value) {
System.out.println("New value: " + value);
}
}
Если необходимо вручную применить конфигурацию на основе аннотаций к некоторому бину (может потребоваться там где нет spring контекста), то можно использовать сервис BeanConsumerService.
Пример теста:
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import ru.citeck.ecos.config.lib.consumer.bean.EcosConfig
import ru.citeck.ecos.config.lib.service.EcosConfigServiceFactory
class CustomComponentTest {
@Test
fun test() {
val services = EcosConfigServiceFactory()
val instance = CustomClass()
// проверяем, что сейчас в field ничего нет
assertThat(instance.field).isNull()
// регистрируем все поля и методы с аннотацией @EcosConfig
services.beanConsumersService.registerConsumers(instance)
// т.к. значения для "some-config-id" мы не проставляли, то ожидаем, что значение все еще null
assertThat(instance.field).isNull()
// проставляем значение конфига в in-memory провайдере
services.inMemConfigProvider.setConfig("some-config-id", "123")
// после проставления значения в одном из провайдеров ожидаем, что поле с аннотацией автоматически заполнилось
assertThat(instance.field).isEqualTo("123")
// получаем значение через EcosConfigService
val value = services.ecosConfigService.getValue("some-config-id").asText()
// проверяем, что значение, которое мы получили из EcosConfigService совпадает с тем, что мы проствляли в провайдере
assertThat(value).isEqualTo("123")
}
class CustomClass {
@EcosConfig("some-config-id")
var field: String? = null
}
}
Создание нового конфига
Для создания нового конфига нужно создать yaml файл по пути src/main/resources/eapps/artifacts/app/config
Например, telegram-authtoken.yml
С таким содержание:
id: telegram-authtoken
name:
ru: Авторизационный токен для Телеграм бота
en: Telegram bot authorization token
value: disabled
При старте конфиг будет создан и появится журнале «Конфигурация ECOS».
Считать этот параметр можно:
Повесив аннотацию на поле:
@EcosConfig("telegram-authtoken")
private String telegramAuthorizationToken;
Можно вместо поля сделать метод
setAuthToken(String telegramAuthorizationToken)
с той же аннотацией, и он будет вызываться при смене конфига (если нужно отслеживать изменение конфига без перезапуска).
Общая архитектура работы конфигураций
TargetBean - это целевой бин с аннотациями
@EcosConfig
;resources - это папка ресурсов в приложении;
some-config.yml - некоторый конфиг в директории
resources/eapps/artifacts/app/config
;Artifacts Source - источник артефактов, который загружает артефакты из папки
resources/eapps/artifacts
;EcosConfigService - сервис конфигураций;
Получение конфигурации при старте системы:
Подключаемся к Zookeeper и проверяем актуальное значение конфигурации там.
Если значение в Zookeeper отсутствует, то загружаем значение из Artifacts Source (т.е. напрямую из classpath);
Если значение найдено, то загружаем его;
Все конфигурации, которое есть в app/config отправляются на микросервис ecos-apps через RabbitMQ (стандартный механизм деплоя артефактов);
Микросервис ecos-apps сохраняет конфигурации у себя в таблице, чтобы в дальнейшем можно было работать с ними через интерфейс (UI);
При этом если в таблице уже есть конфигурация с таким же scope и id, то сравнивается версия конфига. Если новая версия совпадает или меньше текущей, то поле value в таблице не меняется;
После того как поле value у конфигурации в ecos-apps обновилось, микросервис отправляет новое значение в Zookeeper;
Наше приложение подписано на события изменения данных в Zookeeper и когда там меняется значение мы его тут же применяем ко всем слушателям конфигурации.
Когда пользователь в интерфейсе меняет значение конфигурации, то логика аналогична пунктам 3-5, но без проверки версии.
Формы для конфигураций [rc5+]
Для конфигураций есть автогенерация форм на основе значения valueDef:
Сущность конфигурации имеет атрибут
_formRef
, который возвращает ссылку на форму следующего вида -“uiserv/form@config${{config-scope}}${{config-id}}“
, где
{{config-scope}} - область конфигурации
{{config-id}} - идентификатор конфигурации
С этой ссылкой на форму UI отправляет запрос формы на ecos-uiserv и он в свою очередь по префиксу
“config$“
понимает, что форму необходимо вычислить. Вычисление происходит в ConfigFormsProvider
Если же для конфигурации нужна форма со своим набором полей и логикой, то можно использовать поле valueDef.formRef для указания ссылки на любую реальную форму.
Область (scope)
Вся конфигурация имеет scope, который описывает разные области для исключения влияния конфигурации в разных микросервисах друг на друга.
По умолчанию scope равен “app/{{webapp_name}}“, где {{webapp_name}} - это системное имя приложения.
Таким образом уникальным идентификатором конфигурации в системе можно считать только связку scope + config_id (т.е. один и тот же config_id может использоваться в разных областях).
Модель
id: String // идентификатор конфигурации
name: MLText // имя конфигурации
scope: String // область действия конфигурации. По умолчанию "app/{{appName_приложения_в_котором_находится_артефакт}}"
value: Any // значение конфигурации
version: Integer // версия конфигурации. Подробнее ниже.
valueDef: // описание значения в поле value
type: ConfigValueType // тип конфигурации. Если не задан, то будет вычислен автоматически [rc5+] на основе значения в value
multiple: Boolean // флаг "множественное значение"
formRef: RecordRef // форма для редактирования значения
ConfigValueType - одно из следующих значений:
ASSOC,
PERSON,
AUTHORITY_GROUP,
AUTHORITY,
TEXT,
MLTEXT,
NUMBER,
BOOLEAN,
DATE,
DATETIME,
JSON
Версия конфигурации
В конфигурации ECOS есть поле версии, которое нужно для:
Защиты от случайного затирания значения, которое настроено в системе вручную;
Для возможности поменять значение по умолчанию для новых разворачиваемых систем;
Для возможности принудительно поменять значение, которое изменили в системе вручную.
Обновление поля value в уже задеплоеной конфигурации при загрузке новой версии артефакта происходит только если поле version у нового конфига больше чем то что сохранено в БД.
При использовании патчей для артефакта конфигурации следует так же учитывать влияние поля version (т.е. если мы поменяем только value, но version оставим старым, то в системе значение конфига останется прежним).
Config Provider
Вся конфигурация в сервисе EcosConfigService получается из провайдеров, которые имеют следующий интерфейс:
interface EcosConfigProvider {
fun getConfig(key: String): ConfigValue?
fun getConfig(key: ConfigKey): ConfigValue?
fun watch(action: (ConfigEvent) -> Unit)
fun getOrder(): Float
}
Все провайдеры сортируются по значению getOrder() и в итоге EcosConfigService отдает не-null значение из провайдера с наименьшим значением getOrder()
Стандартные провайдеры:
ArtifactsConfigProvider - конфигурация загружается из classpath;
InMemConfigProvider - in-memory провайдер. В основном используется для тестов;
ZkConfigProvider - провайдер на основе Zookeeper.
Обновление значения через патч ECOS
Нужно разместить подобный some-patch.yml в директории resources/eapps/artifacts/app/patch
:
---
id: some-patch
date: '2022-01-01T00:00:00Z'
targetApp: uiserv
type: mutate
config:
record:
id: cfg@active-theme
attributes:
value: customTheme