Scala IRL. Часть 2: Готовим Домашнего Страуса
В предыдущем посте я мельком упомянул библиотеку ostrich в качестве инструмента для загрузки конфигов, в этом же постараюсь сделать более подробный обзор. Итак, ostrich - это внутренняя библиотека от разработчиков “твиттера”, используемая в его компонентах. Несмотря на фичастость и навороченность, местами довольно-таки заметен её стиль, как библиотеки написанной в первую очередь “для себя”.
Оstrich выполняет следующие задачи:
- загрузка и парсинг typesafe-конфигов (проще, говоря, конфигов в виде scala-кода)
- сбор рантайм-статистики и разнообразных метрик
- управление сервисами внутри процесса (запуск/остановка)
- предоставление простенького, но расширяемого администраторского интерфейса через http/socket
В этом посте я опишу работу с конфигами, а остальные возможности попробую раскрыть в последующих постах.
Страус и конфиги
Итак, ostrich предлагает использовать в качестве конфигов не просто структурированные текстовые файлы (json/xml/properties), а scala-код. Такой подход требует компиляции конфига при загрузке, но имеет ряд серьёзных преимуществ:
- типизация. Отпадает необходимость проверять и приводить значения из конфиг-файла к нужным типам
- возможность писать произвольный код в конфигах, помимо простых значений. Например, использовать текущую дату в произвольном формате в имени лог-файла, или получить значения каких-то параметров из базы или стороннего сервиса. По сути, конфиг-файл становится тем, что называется extension point.
- становятся ненужными отдельные классы, инкапсулирующие конфигурацию различных компонентов программы, или, по крайней мере, упрощается их создание.
Для начала, нам нужно создать родительский класс для настроек. Чаще всего удобно сделать его же конфигом по умолчанию. Предположим, мы хотим сконфигурировать небольшое серверное приложение. Для этого ostrich предоставляет класс ServerConfig (являющийся наследником Config, предоставляющего базовые функции такие как компиляция, валидация и обязательные/необязательные поля).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Надо заметить, что мы используем в этом конфиге изменяемые переменные (var
). В Scala это зачастую является признаком недостаточно функционального (декларативного) стиля, однако в данном случае это позволит писать лаконичные конфиги, выставляя значение полей простым присваиванием. Кроме того, этот же стиль используется при объявлении уже имеющихся в ServerConfig значений.
Итак, теперь наш конфиг для, например, тестового сервера, может выглядеть так:
1 2 3 4 5 6 |
|
Вполне лаконично, и ничуть не хуже .properties и уж тем более XML-файла. Можем сохранить его под именем test.scala
, тогда наш сервер мы сможем запускать как
1
|
|
Загрузка
Теперь, наш файл настроек надо загрузить и использовать по назначению. Делать это логичнее всего поближе к точке входа, например в main
. Путь к файлу можно передать с параметром -f
при запуске приложения, а если его нет, то ostrich попытается найти его сам. Определение местоположения конфиг-файла - это то самое место, где становится видно, что проект делался для себя: ostrich ищет его в довольно специфических местах, пытаясь сначала выяснить имя jar-файла. Поэтому, проще всего всегда передавать путь к конфигу через -f
плюс явно указать конфиг по умолчанию. Поскольку конфиг по умолчанию является обычным scala-классом, можно просто создать его инстанс в коде. Вот как выглядит загрузка и использование конфига у меня:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Обратите внимание на строчку (new MyServerConfig)()(runtime)
: сначала мы вызываем метод apply без параметров, а потом вызываем полученную функцию с параметром типа RuntimeEnvironment. При необходимости, этот способ можно расширить для загрузки различных конфигов по умолчанию, например в зависимости от run.mode
в lift.
Я не использовал возможность ostrich объявлять поля как обязательные/опциональные, но промолчать о ней будет, наверное, неправильно. В приведённой к классу Config документации всё довольно просто:
1 2 3 4 5 6 7 8 |
|
Конфигурация компонентов
В вышеприведённом примере мы вынесли настройку базы данных в отдельный класс DBConfig. Иногда, со стороны приложения бывает удобно вынести конфигурацию в отдельный класс, но усложнять структуру конфиг-файла не хочется. Хорошим примером является случай, когда конфигурируемый компонент находится в другом (под)проекте. В этом случае, конфиг можно объявить трейтом и подмешать его в основной конфиг
Представим, что мы хотим добавить в наш север небольшой внутренний почтовый сервис для отсылки писем пользователям.
1 2 3 4 5 6 7 8 9 |
|
Теперь мы просто подмешиваем трейт в наш основной конфиг, перегружая его методы нашими var
ами:
1 2 3 4 5 6 7 8 9 10 |
|
В нашем конфиг-файле (test.scala
) эти поля будут работать точно так же как и все остальные. При инстанциировании менеджера можно просто передать ему общий конфиг:
1 2 3 4 5 6 7 |
|
Также, в отдельный трейт можно вынести готовые значения для настроек если они представляют собой что-то более сложное чем простые значения.
Вот собственно и всё что я хотел рассказать об использовании ostrich для конфигурирования. Я намеренно не стал рассматривать имеющиеся в ServerConfig поля для настройки логгинга, статистики и админки. Мы вернёмся к ним попозже, когда я буду рассказывать о соответствующих фичах “страуса”.