Движок правил wb-rules 1.7: различия между версиями
N.maslov (обсуждение | вклад) (→Модули) |
N.maslov (обсуждение | вклад) (→Модули) |
||
Строка 197: | Строка 197: | ||
Файл сценария scenario.js | Файл сценария scenario.js | ||
<syntaxhighlight lang="js"> | <syntaxhighlight lang="js"> | ||
var m = require(myModule); | var m = require("myModule"); | ||
m.hello("world"); // выведет в лог "Hello from module, world" | m.hello("world"); // выведет в лог "Hello from module, world" | ||
log("The answer is {}", m.answer); // выведет в лог "The answer is 42" | log("The answer is {}", m.answer); // выведет в лог "The answer is 42" | ||
Строка 219: | Строка 219: | ||
Файл /etc/wb-rules/modules/myModule.js | Файл /etc/wb-rules/modules/myModule.js | ||
<syntaxhighlight lang="js"> | <syntaxhighlight lang="js"> | ||
exports.counter = function() { | |||
if (module.static.count === undefined) { | |||
module.static.count = 1; | |||
} | |||
log("Number of calls: {}", module.static.count); | |||
module.static.count++; | |||
}; | |||
</syntaxhighlight> | |||
Файл сценария scenario1.js | |||
<syntaxhighlight lang="js"> | |||
var m = require("myModule"); | |||
m.counter(); | |||
m.counter(); | |||
</syntaxhighlight> | |||
Файл сценария scenario2.js | |||
<syntaxhighlight lang="js"> | |||
var m = require("myModule"); | |||
m.counter(); | |||
m.counter(); | |||
m.counter(); | |||
</syntaxhighlight> | |||
В результате работы двух скриптов в логе окажется 5 сообщений: | |||
<syntaxhighlight> | |||
Number of calls: 1 | |||
Number of calls: 2 | |||
Number of calls: 3 | |||
Number of calls: 4 | |||
Number of calls: 5 | |||
</syntaxhighlight> | </syntaxhighlight> |
Версия 15:36, 7 марта 2017
В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.
Сценарии
Изоляция сценариев
Начиная с версии wb-rules 1.7, каждый файл сценария запускается в своём отдельном пространстве имён - контексте. Таким образом, каждый сценарий может определять свои функции и глобальные переменные без риска изменить поведение других сценариев.
Пример
В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет глобальные переменные и функции.
В предыдущих версиях wb-rules обращение к глобальной переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению. В версиях, начиная с 1.7, поведение строго определено и такое же, как будто сценарий единственный в системе.
В комментариях указан вывод команд log для ранних версий и для актуальной версии.
Сценарий 1 (rules1.js)
test1 = 42;
setTimeout(function myFuncOne() {
log("myFuncOne called");
log("test1: {}, test2: {}", test1, test2);
// раньше: test1: [либо 42, либо 84], test2: Hello
// теперь: test1: 42, test2: (undefined)
// (будет выведена ошибка выполнения: ReferenceError: identifier 'test2' undefined)
}, 1000);
Сценарий 2 (rules2.js)
test1 = 84;
test2 = "Hello";
setTimeout(function myFuncTwo() {
log("myFuncTwo called");
log("test1: {}, test2: {}", test1, test2);
// раньше: test1: [либо 42, либо 84], test2: Hello
// теперь: test1: 84, test2: Hello
}, 1000);
Примечание
В предыдущих версиях wb-rules для изоляции правил рекомендовалось использовать замыкание, т.е. оборачивание кода сценария в конструкцию:
(function() {
// код сценария идёт здесь
})();
Начиная с версии 1.7, в подобной конструкции нет необходимости. Тем не менее, старые сценарии, использующие эту конструкцию, продолжат работу без изменений в поведении.
Обходной путь
Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций, есть несколько способов реализации такого поведения:
Использование модулей
см. Модули
Прототип глобального объекта
ВНИМАНИЕ: метод считается "грязным", т.к. все переменные и функции, опубликованные таким образом, становятся доступными всем сценариям в системе. Старайтесь избегать этого способа. За неопределённое поведение при использовании этого метода несёт ответственность сам программист.
Глобальные объекты всех сценариев имеют общий объект-прототип, в котором определены стандартные функции wb-rules (такие, как defineRule, setTimeout и т.д.). Через него можно передавать переменные или функции в общую область видимости.
global.__proto__.myVar = 42; // теперь myVar - общая переменная для всех сценариев
// из других сценариев к переменной можно обращаться так
log("shared myVar: {}", myVar);
// или вот так, что чуть более аккуратно, т.к. однозначно показывает, где определена переменная
log("shared myVar: {}", global.__proto__.myVar);
Правило поиска переменной в первом случае будет выглядеть так:
- Проверяем, есть ли myVar среди локальных переменных (определённой как var myVar = ...).
- Если нет, проверяем, есть ли myVar в глобальном объекте (определённой как myVar = ...).
- Если нет, проверяем, есть ли myVar в прототипе глобального объекта (определённой как global.__proto__.myVar).
Поиск останавливается, как только переменная найдена.
Таким образом, первый способ обращения будет работать только в том случае, если myVar не определена в верхних областях видимости.
Анонимные правила
Теперь правила можно объявлять анонимно (без задания специального имени). Это позволит уменьшить путаницу и неочевидное поведение системы при дублировании имён правил в одном скрипте.
Уникальные имена для анонимных правил генерируются автоматически.
Старый синтаксис (с явным заданием имени правила) продолжит работу без изменений.
ВНИМАНИЕ: начиная с версии 1.7, при объявлении правил с одинаковыми именами в одном файле теперь будет возвращаться ошибка.
Пример
defineRule({
whenChanged: "mydev/test",
then: function() {
log("mydev/test changed");
}
});
Модули
Начиная с версии 1.7, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на аналогичную в Node.js, но с некоторыми особенностями).
Расположение
Поиск модулей происходит по следующим путям (в заданном порядке):
- /etc/wb-rules/modules
- /usr/share/wb-rules/modules
Таким образом, пользовательские модули удобно складывать в /etc/wb-rules.
Добавить свои пути можно редактированием /etc/default/wb-rules добавлением путей к переменной WB_RULES_MODULES через разделитель (:):
...
WB_RULES_MODULES="/etc/wb-rules/modules:/usr/share/wb-rules/modules"
...
Подключение модуля к сценарию
Подключение модуля происходит с помощью функции require(). Она возвращает объект, экспортированный модулем (exports).
...
var myModule = require("myModule");
...
При этом движок правил будет искать файл myModule.js по очереди в директориях поиска (см. Расположение).
Также допустим поиск файла модуля по поддиректориям в директориях поиска, тогда вызов будет выглядеть так:
...
var myModule = require("path/to/myModule");
...
После того, как файл будет найден, его содержимое будет выполнено, и из файла будет передан объект exports.
Примечание 1: если модуль был подключен в одном сценарии несколько раз (несколько вызовов require("myModule")), содержимое файла модуля будет выполнено только в первый раз, а при повторных вызовах будет возвращаться сохранённый объект exports.
Примечание 2: если модуль подключается в разных сценариях, для каждого сценария будет создан свой объект модуля и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев, для хранения данных следует использовать объект module.static.
Создание модуля
Для создания модуля достаточно создать файл с именем, соответсвующим имени модуля (с расширением .js) в директории /etc/wb-rules/modules.
В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью которого можно реализовать необходимый функционал модуля.
Объект exports
С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.
Пример
Файл модуля /etc/wb-rules/modules/myModule.js
exports.hello = function(text) {
log("Hello from module, {}", text);
};
exports.answer = 42;
Файл сценария scenario.js
var m = require("myModule");
m.hello("world"); // выведет в лог "Hello from module, world"
log("The answer is {}", m.answer); // выведет в лог "The answer is 42"
Объект module
Объект module содержит параметры, относящиеся непосредственно к файлу модуля.
module.filename
Содержит полный путь до файла модуля. Например, для модуля, сохранённого в /etc/wb-rules/modules/myModule.js:
log(module.filename); // выведет /etc/wb-rules/modules/myModule.js
module.static
Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных, которые должны быть доступны сразу во всех сценариях, использующих данный модуль.
Файл /etc/wb-rules/modules/myModule.js
exports.counter = function() {
if (module.static.count === undefined) {
module.static.count = 1;
}
log("Number of calls: {}", module.static.count);
module.static.count++;
};
Файл сценария scenario1.js
var m = require("myModule");
m.counter();
m.counter();
Файл сценария scenario2.js
var m = require("myModule");
m.counter();
m.counter();
m.counter();
В результате работы двух скриптов в логе окажется 5 сообщений:
Number of calls: 1
Number of calls: 2
Number of calls: 3
Number of calls: 4
Number of calls: 5