- "Охотимся за утечками памяти в Node.js"
- "Нагружаем Node под завязку"
- "Храним сессии на клиенте, чтобы упростить масштабирование приложения"
- "Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование"
- "Пишем сервер, который не падает под нагрузкой"
- "Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify"
В этой статье из цикла о Node.js мы рассмотрим модуль node-convict, который помогает управлять конфигурациями приложений Node.js. Он предоставляет прозрачные настройки по умолчанию и встроенную типизацию, чтобы было легче находить и исправлять ошибки.
Постановка задачи
Есть две основные проблемы, которые создают необходимость в конфигурации приложений:
- Большинство приложений могут работать в нескольких окружениях, имеющих разные параметры конфигурации.
- Включение учётных данных и другой конфиденциально информации в код приложения может создавать проблемы.
Эти проблемы можно решить, инициализируя некоторые переменные в зависимости от текущего окружения и используя переменные окружения для хранения конфиденциальных данных. Общепринятый в среде Node.js шаблон для реализации этого подхода состоит в создании модуля, который экспортирует конфигурацию:
var conf = { // окружение приложения - // "production", "development", или "test env: process.env.NODE_ENV || "development", // IP адрес ip: process.env.IP_ADDRESS || "127.0.0.1", // Порт port: process.env.PORT || 0, // Настройки БД database: { host: process.env.DB_HOST || "localhost:8091" } }; module.exports = conf;
Это работает неплохо, но есть ещё пара проблем:
- Что если в конфигурации указаны некорректные данные? Мы можем сберечь время и нервы, обнаруживая ошибки как можно раньше.
- Насколько легко разобраться в конфигурации администраторам, тестировщикам и другим членам большой команды, когда им надо менять настройки или искать дефекты? Более декларативный и лучше документированный формат сделал бы их жизнь легче.
Представляем convict
node-convict решает обе эти проблемы, предоставляя схему конфигурации, в которой можно задавать информацию о типах, значения по умолчанию, переменные окружения и документацию для каждой из настроек.
С использованием convict пример выше принимает такой вид:
var conf = convict({ env: { doc: "The applicaton environment.", format: ["production", "development", "test"], default: "development", env: "NODE_ENV" }, ip: { doc: "The IP address to bind.", format: "ipaddress", default: "127.0.0.1", env: "IP_ADDRESS" }, port: { doc: "The port to bind.", format: "port", default: 0, env: "PORT" }, database: { host: { default: "localhost:8091", env: "DB_HOST" } } }); conf.validate(); module.exports = conf;
Здесь содержится практически та же самая информация, но представленная в виде схемы. Благодаря этому нам удобнее экспортировать её и отображать в удобочитаемом виде, делать валидацию. Декларативный формат делает приложение более надёжным и более дружественным ко всем членам команды.
Как устроена схема
Для каждого параметра настройки есть четыре свойства, каждое из которых помогает сделать приложение надёжнее и проще для понимания:
- Тип. В свойстве
format
указывается или один из встроенных в convict типов (ipaddress
,port
,int
и т.д.) или функция для валидации пользовательских типов. Если во время валидации параметр не проходит проверку типа, возникает ошибка. - Значения по умолчанию. Каждый параметр должен иметь значение по умолчанию.
- Переменные окружения. Если переменная, указанная в
env,
установлена, то её значение будет использовано вместо значения по умолчанию. - Документация. Свойство
doc
вполне очевидно. Преимущество включения документации в схему перед комментариями в коде состоит в том, что эту информация используется в методеconf.toSchemaString()
для более информативного вывода.
Дополнительные уровни конфигурации
Над фундаментом из значений по умолчанию можно надстраивать дополнительные уровни конфигурации с помощью вызовов conf.load()
и conf.loadFile()
. Например, можно загружать дополнительные параметры из объекта JavaScript для конкретного окружения:
var conf = convict({ // схема та же, что и в предыдущем примере }); if (conf.get('env') === 'production') { // в боевом окружении используем другой порт и сервер БД conf.load({ port: 8080, database: { host: "ec2-117-21-174-242.compute-1.amazonaws.com:8091" } }); } conf.validate(); module.exports = conf;
Или же можно создать отдельные конфигурационные файлы для каждого из окружений, и загружать их с помощью conf.loadFile()
:
conf.loadFile('./config/' + conf.get('env') + '.json');
loadFile()
также может загружать несколько файлов сразу, если передать массив аргументов:
// CONFIG_FILES=/path/to/production.json,/path/to/secrets.json,/path/to/sitespecific.json conf.loadFile(process.env.CONFIG_FILES.split(','));
Загружать дополнительные параметры через load()
и loadFile()
полезно, когда есть настройки для каждого из окружений, которые не стоит устанавливать в переменных окружения. Отдельные декларативные конфигурационные файлы в формате JSON позволяют нагляднее представить различия между параметрами в разных окружениях. А так как файлы загружаются с помощью cjson, они могут содержать комментарии, что делает их ещё более понятными.
Обратите внимание, что переменные окружения имеют наивысший приоритет, выше, чем настройки по умолчанию и настройки, загруженные через load()
и loadFile()
. Чтобы проверить, какие именно настройки действуют, можно вызвать conf.toString()
.
«V» — значит валидация
После того, как настройки загружены, можно запустить валидацию, чтобы проверить, все ли они имеют правильный формат в соответствии со схемой. В convict есть несколько встроенных форматов, таких как url
, ports
или ipaddress
, кроме того можно использовать встроенные конструкторы JavaScript (например Number
). Если свойство format
не задано, convict проверит тип параметра на совпадение с типом значения по умолчанию (вызвав Object.prototype.toString.call). Приведённые ниже три схемы эквивалентны:
var conf1 = convict({ name: { format: String default: 'Brendan' } }); // если формат не указан, предполагаем, что тип должен быть // такой же, как у значения по умолчанию var conf2 = convict({ name: { default: 'Brendan' } }); // более лаконичная версия var conf3 = convict({ name: 'Brendan' });
Формат можно задать и в виде перечисления, в явном виде задав перечень допустимых значений, например ["production", "development", "test"]
. Любое значение, которого нет в списке, не пройдёт валидацию.
Вместо встроенных типов, можно использовать собственные валидаторы. К примеру, мы хотим, чтобы параметр был строкой из 64 шестнадцатеричных цифр:
var check = require('validator').check; var conf = convict({ key: { doc: "API key", format: function (val) { check(val, 'should be a 64 character hex key').regex(/^[a-fA-F0-9]{64}$/); }, default: '3cec609c9bc601c047af917a544645c50caf8cd606806b4e0a23312441014deb' } });
Вызов conf.validate()
возвратит детальную информацию о каждой ошибочной настройке, если такие есть. Это помогает избежать повторного развёртывания приложения при обнаружении каждой ошибки в конфигурации. Вот как будет выглядеть сообщение об ошибке, если мы попытаемся присвоить параметру key
из предыдущего примера значение 'foo'
:
conf.set('key', 'foo'); conf.validate(); // Error: key: should be a 64 character hex key: value was "foo"
Заключение
node-convict расширяет стандартный шаблон конфигурирования приложений Node.js, делая его более надёжным и удобным для членов команды, которым не придётся разбираться в дебрях императивного кода, чтобы проверять или изменять настройки. Схема конфигурации даёт команде проекта больше контекста для каждой настройки и позволяет делать валидацю для раннего обнаружения ошибок в конфигурации.
Продолжение следует…
ссылка на оригинал статьи http://habrahabr.ru/company/nordavind/blog/197166/
Добавить комментарий