Страницы

пятница, 24 февраля 2017 г.

Node.js at scale. Configuration

Вчера мне попался на глаза один пост с интригующим заголовком: "О структуре и масштабировании сложных приложений Node.js". После прочтения сжечь хочу рассказать о своем варианте управления конфигурацией "сложных приложений" Node.js. Не смотря на то, что в npm есть целый ряд модулей (npm search configuration), самый известный из которых, nconf, умеет не только миксовать process.argv, process.env и содержимое файлов (по моему основной функционал подобных решений), но и еще много чего (ненужного на мой взгляд), я написал свой велосипед. Знакомьтесь, hackee.

Допустим у нас есть файл конфигурации "сложного приложения":
- conf.js:
module.exports = {
  host: 'localhost',
  port: 80,
  db: { connection: undefined },
  date: undefined,
  object: { a: 1, b: undefined, c: 3 },
  array: ['1', '2', undefined]
};

В результате мы хотим получить следующую конфигурацию:
- expected.js:
module.exports = {
  host: '127.0.0.1',
  port: 8080,
  db: { connection: 'string', client: 'client' },
  date: new Date('2017-01-01T00:00:00.000Z'),
  object: { a: 1, b: 2, c: 30 },
  array: ['1', '20', '3']
};

Запускаем приложение:
- app.js:
const hackee = require('hackee');
const conf = hackee('./conf');
console.log(conf);


Модуль выкинул ошибку, т.к. значения undefined свойств объекта экспорта файла conf.js говорят о том, что приложение ждет эти значения либо в process.argv либо в process.env.

Можно запретить модулю выкидывать эту ошибку, но это не то что нам нужно:
- app.js:
const hackee = require('hackee');
const conf = hackee({ file: './conf', required: { error: false } });
console.log(conf);


Можно изменить значение по умолчанию флага, сообщающего модулю о том, какие значения приложение ждет из process.argv или process.env, но это тоже не то что нам нужно, т.к. в process.argv нет всех необходимых ингредиентов:
- app.js:
const hackee = require('hackee');
const conf = hackee({ file: './conf', required: { value: null } });
console.log(conf);


Можно изменить приоритет сборки конфигурации (по умолчанию [process.argvprocess.env, file]) и поставить file на первое место, но это тоже не то что нам нужно:
- app.js:
const hackee = require('hackee');
const conf = hackee({ file: './conf', priority: 'file', required: { value: null } });
console.log(conf);


Все что нам нужно, это установить переменные окружения и запустить приложение с аргументами:

- app.js:
const hackee = require('hackee');
const conf = hackee('./conf');
console.log(conf);


Вместо пути к файлу можно использовать объект, результат будет такой же:
- app.js:
const hackee = require('hackee');
const file = require('./conf');
const conf = hackee({ file });
console.log(conf);


После инициализации конфигурацию можно использовать в любом модуле приложения:
- app.js:
const hackee = require('hackee');
const assert = require('assert');
const expected = require('./expected');
// инициализируем
const conf = hackee('./conf');
conf.db.client = 'client';
assert.deepStrictEqual(conf, expected);
// используем
require('./module');

- module.js:
const conf = require('hackee');
const assert = require('assert');
const expected = require('./expected');
const {
  host,
  port,
  db: { connection, client },
  date,
  object: { a, b, c },
  array: [first, second, third],
} = conf;
assert.strictEqual(host, expected.host);
assert.strictEqual(port, expected.port);
assert.strictEqual(connection, expected.db.connection);
assert.strictEqual(client, expected.db.client);
assert.deepStrictEqual(date, expected.date);
assert.strictEqual(a, expected.object.a);
assert.strictEqual(b, expected.object.b);
assert.strictEqual(c, expected.object.c);
assert.strictEqual(first, expected.array[0]);
assert.strictEqual(second, expected.array[1]);

Можно смиксовать несколько путей (и | или) объектов:
- conf2.js:
module.exports = {
  host: 'localhost',
  port: 80,
  db: { connection: undefined },
  date: undefined,
  object: { a: 100, b: 200, c: 300 },
  array: ['100', '200', '300']
};

- app.js:
const hackee = require('hackee');
const file = require('./conf');
const conf = hackee({ file: [file, './conf2'] });
console.log(conf);


Можно создать несколько конфигураций, присвоить им алиасы, и даже итерировать по созданным конфигурациям:
- app.js:
const hackee = require('hackee');
const assert = require('assert');
const conf1 = hackee({ file: './conf', alias: 'argv' });
const conf2 = hackee({ file: './conf', priority: 'file', alias: 'file' });
const confs = [...hackee];
console.log(confs[0]);
console.log(confs[1]);
assert.strictEqual(confs.length, 2);
assert.deepStrictEqual(conf1, confs[0].value);
assert.deepStrictEqual(hackee({ alias: 'argv' }), confs[0].value);
assert.deepStrictEqual(conf2, confs[1].value);
assert.deepStrictEqual(hackee({ alias: 'file' }), confs[1].value);
assert.strictEqual([...hackee].length, 2);


That's all folks.