Рубрики
Без рубрики

Разработка библиотеки для чтения многоуровневых настроек приложений на Java

“Фотография Зигмунда на снимке” 🔔 Эта статья была первоначально опубликована на моем сайте, MihaiBojin.com …. Помеченный настройками, свойствами, java, десериализацией.

“Фото автора Зигмунд на Расплескать “

🔔 Эта статья была первоначально опубликована на моем сайте, MihaiBojin.com . 🔔

В течение последнего года я то и дело думал об общей проблеме: обработке свойств в Java. Хотя проблема кажется простой, работа с несколькими источниками, опциональностью, секретами и т.д. Не является тривиальной.

Я начал с поиска существующих реализаций, но не смог найти многих.

Самым близким, что я нашел, был Netflix Archaius , но он не делал всего, что мне было нужно, и имел функции, которые мне не нужны. Однако сделка завершилась тем, что библиотека казалась заброшенной, а последний незначительный релиз состоялся в 2019 году.

Я решил создать свой собственный ( #NIH , я знаю 😝 ).

Первая версия

Моя первая попытка привела к созданию библиотеки props . Для этого у меня было несколько целей.

Мне нужна была возможность загружать свойства из нескольких источников : Файлы свойств Java , Свойства системы , Переменные среды ОС и т.д.

Поскольку эти источники основаны на тексте, значения сначала загружаются в виде строк. Однако строки могут быть десериализованы во многие типы, чего и следовало ожидать от приложения. Следовательно, мы хотим, чтобы такая библиотека преобразовывала значения в соответствующие (стандартные или определяемые пользователем) типы. Мы могли бы делать это каждый раз, когда считывается значение. Однако выполнение этого при каждом чтении приведет ко множеству ненужных преобразований типов. Лучший подход состоит в том, чтобы привести один раз и запомнить введенное значение до тех пор, пока оно не изменится.

Помимо своего значения, a prop имеет другие атрибуты (метаданные) . Метаданные включают:

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

Ключ может быть определен более чем в одном источнике, поэтому библиотеке необходимо определить, какому источнику принадлежит какой ключ .

Затем значения могут измениться в источнике; для решения этой проблемы нам понадобится механизм для проверки обновлений и периодического обновления любых обновленных ключей.

Мы также хотим учесть расширяемость . Например, клиенту этой библиотеки может потребоваться определить новые источники (например, прочитать свойства из базы данных MongoDB или конечной точки HTTP). С этой целью мы предоставляем интерфейс, который пользователи могут реализовать для добавления дополнительных функций.

Другой вариант использования – управление секретами . Хотя лучше всего использовать готовые решения (например, сервер на базе KMIP ), некоторые приложения требуют определения ключей в файлах свойств. В этом случае лучше всего зашифровать их в состоянии покоя. Шифрование генерирует двоичный вывод; распространенным способом представления двоичных данных в виде текста (например, в файле свойств) является их кодирование в формате base64. Наша библиотека должна иметь возможность легко десериализовать зашифрованное значение в кодировке base64 в виде строки.

Поскольку я хотел использовать эту библиотеку в проектах Java, я настроил конвейер компакт-дисков, чтобы выпустить ее в Maven central .

Я построил Props v1 с учетом вышеизложенного.

Архитектурная проблема

Однако я был слишком сосредоточен на создании доказательства концепции и не слишком задумывался о производительности. Я также создал эту первую версию с учетом статического варианта использования (клиент отвечает за обновление значения prop .)

Однако бывают случаи, когда клиенту также полезно получать уведомления об изменении значений. Поняв это, я добавил поддержку уведомления подписчиков об обновлениях. Я также начал смотреть на то, где были узкие места. Хотя библиотека ни в коем случае не является медленной, несколько оригинальных решений затрудняют ее улучшение.

Это распространенная проблема в разработке программного обеспечения. Для устоявшихся библиотек со значительным внедрением пользователями перепроектирование с нуля обходится дорого. Обычно код обновляется медленно, с большой осторожностью, чтобы избежать серьезных изменений.

Поскольку это личный проект без текущего производственного использования, у меня есть немного гибкости.

Я решил переписать код с учетом производительности. Однако я также хочу сосредоточиться на асинхронных примитивах, которые являются лучшим выбором для современного программного обеспечения.

Представьте себе следующие сценарии:

A. Свойство загружается один раз из файла конфигурации и используется для настройки максимального количества запросов, разрешенных приложением за заданный интервал. Когда приложение необходимо перенастроить, файл обновляется, и приложение перезапускается. Перезапуск не происходит мгновенно, и это может привести к простою пользователей. Чтобы избежать этого, необходимо запускать несколько экземпляров и управлять ими с помощью внешней службы оркестровки (например, Kubernetes).

B. Одно и то же свойство знает о своем происхождении и может уведомлять код реализации о любых обновлениях. Затем приложение может перенастроить себя без полного перезапуска.

Конечно, нет 100% правильного решения. Если платформа уже управляет приложением, развернуто и запущено несколько экземпляров, и если перезапуск службы выполняется достаточно быстро, решение A может быть вполне жизнеспособным. Однако в большинстве случаев, по крайней мере, возможность использовать решение B является положительным чистым преимуществом: оно есть, но вам не нужно его использовать. Однако отсутствие этого может быть ограничивающим фактором. Эта логика может быть расширена до замены секретов, включения новой функции для растущего числа пользователей и т.д.

Как бы выглядела лучшая реализация?

Глоссарий терминов

Давайте определим несколько общих терминов:

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

Конструктивные соображения

Давайте обрисуем несколько соображений по дизайну.

Источники должны быть многоуровневыми в том смысле, что они должны устанавливать четкий приоритет над тем, какой источник в конечном итоге владеет каким ключом .

Когда значение обновляется в источнике , и этот источник владеет реквизитом , реквизит необходимо (в конечном итоге) обновить новым значением. ” В конечном счете” означает, что prop может не видеть все переходы состояний (записи) между чтениями. Важно отметить, что Я не разрабатываю решение для потоковой передачи событий . Поэтому некоторые обновления могут никогда не дойти до клиента.

Нам нужно выбрать сопоставление 1:1 или 1:много между ключами и реквизитами объектами в реестре . С одной стороны, разрешение использовать более одной опоры для любого заданного ключа может быть гибким. Например:

var prop1 = new StringProp("my.key");
var prop2 = new StringProp("my.key");

Несколько абонентов могут связывать и использовать новые объекты prop без каких-либо ограничений. Однако я считаю, что этот выбор позволит пользователям писать неэффективный код.

Следующий вопрос: должны ли мы разрешить привязку реквизитов разных типов к одному и тому же ключу ?

Рассмотрим следующий код:

var prop1 = new DurationProp("my.key");
var prop2 = new LongProp("my.key");
var prop3 = new StringProp("my.key");

Вы могли бы утверждать, что необходимы все три формы, но то же самое может быть достигнуто следующим образом:

var prop = new DurationProp("my.key");
Duration result = prop.get();
Long seconds = result.toSeconds();
String stringValue = result.toString();

Асинхронные обновления должны распространяться быстро. Нет смысла внедрять такую библиотеку только для поддержки нескольких обновлений в секунду или только нескольких десятков ключей, определенных в системе.

Давайте сосредоточимся на производительности, ограничив библиотеку одним prop объектом на ключ , чтобы количество объектов prop было небольшим, а реализация – скудной.

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

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

1 Реквизит определяется источником 1 Источник 1,
2 Реквизит определяется источником 2 Источник 2,
2 Реквизит обновлен источником 1 Источник 2,
4 Реквизит обновлен источником 2 Источник 2,
3 Реквизит удален из источника 1 Источник 2
4 Реквизит определяется источником 1 Источник 2,
5 Реквизит удален из источника 2 Источник 1
6 Реквизит определяется источником 2 Источник 2,
5 Реквизит удален из источника 2 Источник 1
нулевой Реквизит удален из источника 1 Н/Д

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

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

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

  • эффективно справляться с быстрой сменой собственников
  • отметьте, когда происходит такое поведение, чтобы оператор-человек мог проверить правильность

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

Другой вопрос, должны ли мы с нетерпением или лениво обновлять каждый ключ?

Несмотря на то, что несколько источников потенциально могут определять тысячи пар ключ-значение, это не обязательно означает, что эти значения всегда считываются или обновляются с одним и тем же интервалом.

Нетерпеливо установление права собственности на источник и обновление эффективной стоимости каждого ключа, вероятно, будет дорогостоящим (с точки зрения производительности).

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

Для реализации потребуется эффективный способ отслеживания ключ владение.

Например, когда требуется значение, было бы лучше, если бы реестр мог напрямую перейти к источнику , которому принадлежит этот ключ, вместо того, чтобы сначала находить (перебирать все источники за O (N) время) соответствующий источник. Соответственно, когда значения источника обновляются, ему также необходимо эффективно публиковать обновления для всех свойств объекты, которыми он владеет, пропуская те, которыми он не владеет.

Вывод

Я мог бы написать больше, но на данный момент эта статья, надеюсь, подготовила бы почву для того, что будет делать эта библиотека.

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

Я планирую (переписать) эту библиотеку Java с нуля и документировать все это, включая такие темы, как:

  • настройка репозитория GitHub
  • настройка системы сборки
  • настройка конвейера CI/CD
  • производительность написания, хорошо протестированный код
  • отправка релизов в общедоступное хранилище Maven (например, Maven Central)
  • форматирование кода в соответствии с общим стандартом
  • выравнивание кода
  • сравнительный анализ и документирование базовых показателей эффективности
  • и другие

Следите за обновлениями, чтобы узнать больше, и не забудьте подписаться на мою рассылку новостей если вы хотите получать все обновления об этой серии по электронной почте!

До следующего раза…

Михай

Оригинал: “https://dev.to/mihaibojin/designing-a-library-for-reading-layered-application-settings-in-java-2egf”