Приручаем конфигурации веб-приложений с помощью node-convict

в 15:17, , рубрики: mozilla persona, node-convict, node.js, node.js holiday season, Блог компании Нордавинд, переменные окружения, разработка

Приручаем конфигурации веб приложений с помощью node convictОт переводчика: Это седьмая статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona.


В этой статье из цикла о 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;

Здесь содержится практически та же самая информация, но представленная в виде схемы. Благодаря этому нам удобнее экспортировать её и отображать в удобочитаемом виде, делать валидацию. Декларативный формат делает приложение более надёжным и более дружественным ко всем членам команды.

Как устроена схема

Для каждого параметра настройки есть четыре свойства, каждое из которых помогает сделать приложение надёжнее и проще для понимания:

  1. Тип. В свойстве format указывается или один из встроенных в convict типов (ipaddress, port, int и т.д.) или функция для валидации пользовательских типов. Если во время валидации параметр не проходит проверку типа, возникает ошибка.
  2. Значения по умолчанию. Каждый параметр должен иметь значение по умолчанию.
  3. Переменные окружения. Если переменная, указанная в env, установлена, то её значение будет использовано вместо значения по умолчанию.
  4. Документация. Свойство 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, делая его более надёжным и удобным для членов команды, которым не придётся разбираться в дебрях императивного кода, чтобы проверять или изменять настройки. Схема конфигурации даёт команде проекта больше контекста для каждой настройки и позволяет делать валидацю для раннего обнаружения ошибок в конфигурации.


Продолжение следует...

Автор: ilya42

Источник

Поделиться