HOCON — конфигурируем гибко

77d87fea62aa4ef9adea40cfe379dede.jpg

Хранение параметров программ в текстовых конфигах — задача довольно частая и на первый взгляд тривиальная. Многие тут же хмыкнут:, а в чем проблема-то? Есть куча форматов (и библиотек для работы с ними): properties, XML, JSON, YAML. В чем хочешь — в том и храни. Делов-то.

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

HOCON — это opensource-библиотека на Java, читающая конфиг-файлы из формата, основанного на JSON. По сравнению с JSON этот формат менее строгий и обладает дополнительными возможностями:

{
    // Можно писать комментарии
    "a" : 42 // Можно пропускать запятые в конце строки
    b: hello // Можно пропускать кавычки
}

Однако основная ценность HOCON — это копирование значений переменных и даже целых JSON-объектов.
{
    a: 42
    b: ${a} // Присваиваем переменной b значение переменной a
    c : {
        m1 : 1
        m2 : 2
    }
    d : ${c} { // Копируем в d значение из c
        m3 : 3 // Добавляем в d переменную m3 = 3
    }
}

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

Пример 1. Из жизни админов


Мы разрабатываем игровые серверы. А игровые серверы — это целый зоопарк сервисов, которые в зависимости от требований могут работать на разном наборе железа и в различных раскладках. В качестве примера приведу схему раскладки сервисов по хостам с одного из моих прошлых проектов:

89615504383845eead0e6490a5da2022.png

И у всех этих сервисов, разумеется, надо настраивать целую кучу параметров: тут и всякие сетевые адреса, и имена баз данных, пользователи, доступы и бог знает что еще. Параметров этих приблизительно 100500.

Пусть для примера у нас есть три сервиса s1, s2, s3, у которых надо настроить IP-адрес:

{
    s1 :
    {
        ip: "192.168.10.1”
    }
    s2 :
    {
        ip: "192.168.10.1”
    }
    s3 :
    {
        ip: "192.168.10.1”
    }
}

Очень часто эти сервисы запускаются на одном и том же хосте и имеют один и тот же IP-адрес. И мы не хотим при смене IP-адреса лазить по всему конфигу и везде их менять (помним о том, что в жизни их не три, а 100500). Что же делать? Если хранить конфиг в обычном JSON, то можно было бы завести общий параметр host_ip и написать примерно такой код:
ip = config.getValue("s1.ip”);
if ( ip == null ) {
    ip = config.getValue("host_ip”);
}

Однако такое решение имеет существенные недостатки:
  1. Разработчик сервиса может его не предусмотреть для данного конкретного случая.
  2. Эта логика скрыта от администратора, который настраивает конфиги. Откуда ему знать, что если параметр s1.ip не указан, то он будет взят из параметра host_ip? Если же параметров много и они станут часто выделывать подобные фокусы, то с администратором может случиться сердечный приступ (и его тень будет по ночам являться разработчику).

На HOCON же решение полностью прозрачно:
{
    host_ip: "192.168.10.1”
    s1 :
    {
        ip: ${host_ip}
    }
    s2 :
    {
        ip: ${host_ip}
    }
    s3 :
    {
        ip: ${host_ip}
    }
}

Пример 2. Из жизни разработчиков


При разработке задача развернуть новый сервер с нуля возникает не так уж и редко. Вывели новую ветку. Наняли нового программиста. Полетел старый жесткий диск, и надо все ставить заново. Поскольку локальный сервер — это тоже полноценный зоопарк со всеми сервисами (хоть и по одной штуке), то, конечно же, настраивать конфиг каждый раз с нуля совсем не хочется. Напрашивается вариант хранить общий конфиг в системе контроля версий вместе с кодом, а при развертке локального сервера менять в нем только IP хоста. Ну что ж, воспользуемся решением из прошлого примера:
{
    host_ip: "192.168.10.1”
    s1 :
    {
        ip: ${host_ip}
    }
    s2 :
    {
        ip: ${host_ip}
    }
    s3 :
    {
        ip: ${host_ip}
    }
}

Но тут возникает загвоздка: общий конфиг-то лежит под системой контроля версий! Если я поменяю в нем host_ip, то возникнет куча неудобств: постоянно висит дифф, надо мержить изменения, внесенные другими. Еще не дай бог случайно закоммитишь свой host_ip в общую версию. Можно также хранить общий конфиг где-то в другом месте и подкладывать в нужное. Но как тогда туда будут попадать изменения, сделанные в версии другими разработчиками?

И тут нам на помощь приходит директива include:

{
    host_ip: "127.0.0.1” // Пусть по умолчанию открываются локально
    s1 :
    {
        ip: ${host_ip}
    }
    s2 :
    {
        ip: ${host_ip}
    }
    s3 :
    {
        ip: ${host_ip}
    }
    include "local_config.conf” // Подключаем параметры из файла local_config.conf
}

А дальше рядом с основным конфиг-файлом мы подкладываем файл local_config.conf с таким содержимым:
{
    host_ip: "192.168.10.1” // Переопределяем IP на наше значение
}

Файл local_config.conf игнорируется системой контроля версий, никаких конфликтов не происходит.

Заключение


Рассмотренные примеры использования взяты из реальной разработки. И конечно же, возможности HOCON ими не ограничиваются. Фактически HOCON — это не просто текстовый формат, а, скорее, узкоспециализированный скрипт для конфигов, который может существенно облегчить жизнь администратору и разработчикам.

Приводить более подробное описание я тут специально не стал: все прекрасно изложено в официальном руководстве. Если у вас есть свои достойные внимания случаи использования этой библиотеки — делитесь ими в комментариях!

Комментарии (0)

© Habrahabr.ru