
Хранение параметров программ в текстовых конфигах — задача довольно частая и на первый взгляд тривиальная. Многие тут же хмыкнут: а в чем проблема-то? Есть куча форматов (и библиотек для работы с ними): properties, XML, JSON, YAML. В чем хочешь — в том и храни. Делов-то.
Однако масштабы вынуждают посмотреть на это иначе. В частности, после многолетней разработки игровых серверов на Java я постепенно пришел к выводу, что управление конфигами не настолько уж банально. В этой статье речь пойдет о формате HOCON — какие возможности он предоставляет и почему в последнем проекте мы стали пользоваться именно им. Если конкретнее, то мы используем Typesafe Config — opensource-библиотеку написанную на Java.
HOCON — это формат конфиг-файлов, основанный на 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. Из жизни админов
Мы разрабатываем игровые серверы. А игровые серверы — это целый зоопарк сервисов, которые в зависимости от требований могут работать на разном наборе железа и в различных раскладках. В качестве примера приведу схему раскладки сервисов по хостам с одного из моих прошлых проектов:

И у всех этих сервисов, разумеется, надо настраивать целую кучу параметров: тут и всякие сетевые адреса, и имена баз данных, пользователи, доступы и бог знает что еще. Параметров этих приблизительно 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”); }
Однако такое решение имеет существенные недостатки:
- Разработчик сервиса может его не предусмотреть для данного конкретного случая.
- Эта логика скрыта от администратора, который настраивает конфиги. Откуда ему знать, что если параметр 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 — это не просто текстовый формат, а, скорее, узкоспециализированный скрипт для конфигов, который может существенно облегчить жизнь администратору и разработчикам.
Приводить более подробное описание я тут специально не стал: все прекрасно изложено в официальном руководстве. Если у вас есть свои достойные внимания случаи использования этой библиотеки — делитесь ими в комментариях!

