- PVSM.RU - https://www.pvsm.ru -
Здравствуйте, уважаемые читатели.
Нас заинтересовала книга "Node.js Design Patterns [1]", собравшая за год существования очень положительные отзывы, небанальная и содержательная.
Учитывая, что на российском рынке книги по Node.js можно буквально пересчитать по пальцам, предлагаем ознакомиться со статьей автора этой книги. В ней господин Кассиаро делает очень информативный экскурс в свою работу, а также объясняет тонкости самого феномена «паттерн» на материале Node.js.
Поучаствуйте, пожалуйста, в опросе.
Кроме классических паттернов проектирования [2], которые всем нам приходилось изучать и использовать на других платформах и в других языках, специалистам по Node.js то и дело приходится реализовывать в коде такие приемы и паттерны, которые обусловлены свойствами языка JavaScript и самой платформы
Преуведомление
Разумеется, паттерны проектирования, описанные бандой четырех, Gang of Four по-прежнему обязательны для создания правильной архитектуры. Но ни для кого не секрет, что в JavaScript нарушаются практически все правила, усвоенные нами в других языках. Паттерны проектирования – не исключение, будьте готовы, что в JavaScript придется переосмыслить старые правила и изобрести новые. Традиционные паттерны проектирования в JavaScript могут реализовываться с вариациями, причем обычные программерские уловки могут дорасти до статуса паттернов, поскольку они широко применимы, известны и эффективны. Кроме того, не удивляйтесь, что некоторые признанные антипаттерны [3] широко применяются в JavaScript/Node.js (например, часто упускается из виду правильная инкапсуляция [4], так как получить ее сложно, и она зачастую может приводить к «объектному разврату [5]», он же – антипаттерн «паблик Морозов».
Список
Далее следует краткий список распространенных паттернов проектирования, используемых в приложениях Node.js. Я не собираюсь вновь вам показывать, как реализуются на JavaScript «Наблюдатель [6]» или «Одиночка [7]», а хочу заострить внимание на характерных приемах, используемых в Node.js, которые можно обобщить под названием «паттерны проектирования».
Этот список я составил на материале из собственной практики, когда писал приложения Node.js и изучал код коллег, поэтому он не претендует ни на полноту, ни на окончательность. Любые дополнения приветствуются.
Причем я не удивлюсь, что вы уже встречали некоторые из этих паттернов или даже использовали их.
Требование директории (псевдо-плагины)
Этот паттерн – определенно один из самых популярных. Он заключается в том, чтобы потребовать все модули из директории, только и всего. При всей простоте это один из самых удобных и распространенных приемов. В Npm есть множество модулей, реализующих этот паттерн: хотя бы require-all [8], require-many [9], require-tree [10], require-namespace [11], require-dir [12], require-directory [13], require-fu [14].
В зависимости от способа использования требование директории можно трактовать как простую вспомогательную функцию или своеобразную систему плагинов, где зависимости не являются жестко закодированными в требующий модуль, а внедряются из содержимого директории.
Простой пример
var requireDir = require('require-all');
var routes = requireDir('./routes');
app.get('/', routes.home);
app.get('/register', routes.auth.register);
app.get('/login', routes.auth.login);
app.get('/logout', routes.auth.logout);
Более сложный пример (сниженная связность, расширяемость)
var requireFu = require('require-fu');
requireFu(__dirname + '/routes')(app);
Где каждая из /routes
– это функция, определяющая собственный url-маршрут:
module.exports = function(app) {
app.get("/about", function(req, res) {
// работаем
});
}
Во втором примере можно добавить новый маршрут, просто создав новый модуль, без необходимости изменять требующий модуль. Эта практика очевидно более мощная, кроме того, она уменьшает связность между требующими и требуемыми модулями.
Объект Приложение (самодельное внедрение зависимости)
Этот паттерн также очень распространен в других языках/на других платформах, но, в силу динамической природы JavaScript, этот паттерн оказывается очень эффективен (и популярен) в Node.js. В данном случае мы создаем один объект, который служит костяком всего приложения. Обычно этот объект инстанцируется на входе в приложение и служит клеем для различных прикладных сервисов. Я бы сказал, что он очень напоминает Фасад [15], но в Node.js он также широко применяется при реализации очень примитивного контейнера для внедрения зависимостей.
Типичный пример этого паттерна: в приложении есть объект App
(либо объект, одноименный самому приложению), и все сервисы после инициализации прикрепляются к этому большому объекту.
Пример
var app = new MyApp();
app.db = require('./db');
app.log = new require('./logger')();
app.express = require('express')();
app.i18n = require('./i18n').initialize();
app.models = require('./models')(app);
require('./routes')(app);
Затем App object
можно передавать по мере необходимости, чтобы им пользовались другие модули, либо он может принимать форму аргумента функции или require
Когда большинство зависимостей приложения прикреплены к этому стержневому объекту, зависимости фактически внедряются извне в те модули, которые его используют.
Но будьте внимательны: если пользоваться этим паттерном без обеспечения уровня абстракции над загруженными зависимостями, то у вас может получиться всезнающий объект, который сложно поддерживать и который, в принципе, по всем признакам напоминает антипаттерн God object [16].
К счастью, есть некоторые библиотеки, которые помогают справиться с этой проблемой – например, Broadway [17], архитектурный фреймворк, реализующий очень аккуратный вариант этого паттерна, обеспечивающий хорошую абстракцию и позволяющий лучше контролировать жизненный цикл сервиса.
Пример
var app = new broadway.App();
app.use(require("./plugins/helloworld"));
app.init(...);
app.hello("world");
// ./plugins/helloworld
exports.attach = function (options) {
// "this" – это наш объект приложения!
this.hello = function (world) {
console.log("Hello "+ world + ".");
};
};
Перехват функций (латание по-обезьяньи плюс AOP)
Перехват функций – еще один паттерн проектирования, типичный для динамических языков вроде JavaScript – как вы догадываетесь, он очень популярен и в Node.js. Он заключается в дополнении поведения функции (или метода) путем перехвата его (ее) выполнения. Обычно такой прием позволяет разработчику перехватить вызов до выполнения (prehook) или после (post hook). Тонкость заключается в том, что Node.js часто используется в комбинации с обезьяньим латанием [18], и эта техника оказывается очень мощной, но, в то же время, и опасной.
Пример
var hooks = require('hooks'),
Document = require('./path/to/some/document/constructor');
// Добавить методы перехвата: `hook`, `pre`и `post`
for (var k in hooks) {
Document[k] = hooks[k];
}
Document.prototype.save = function () {
// ...
};
// Определяем промежуточную функцию, которая будет вызываться после 'save'
Document.post('save', function createJob (next) {
this.sendToBackgroundQueue();
next();
});
Если вы когда-либо работали с Mongoose [19], то определенно видели этот паттерн в действии; если нет — в npm найдется масса подобных модулей [20] на любой вкус. Но это еще не все: в сообществе Node.js термин «аспектно-ориентированное программирование» (AOP) зачастую считается синонимом перехвата функций, загляните в npm [21] – и поймете, о чем я. Можно ли в самом деле называть это AOP? Мой ответ – НЕТ. AOP требует, чтобы мы применяли сквозную ответственность к срезу [22], а не прикрепляли вручную конкретное поведение к отдельной функции (или даже набору функций). С другой стороны, в гипотетическом AOP-решении на Node.js вполне могли бы применяться перехваты – тогда совет (advice) распространялся бы на множество функций, объединенных, к примеру, одним срезом, определяемым при помощи регулярного выражения. Все модули просматривались бы на соответствие этому выражению.
Конвейеры (промежуточный код)
Это суть Node.js. Конвейеры присутствуют здесь повсюду, отличаются по форме, назначению и вариантам использования. В принципе, конвейер — это ряд соединенных друг с другом обрабатывающих модулей, где вывод одного модуля служит вводом для другого. В Node.js это зачастую означает, что в программе будет ряд функций вида:
function(/* input/output */, next) {
next(/* err and/or output */)
}
Возможно, вы привыкли называть такие вещи промежуточным кодом (middleware) имея в виду Connect [23] или Express [24], но границы использования данного паттерна гораздо шире. Например, Hooks [25] – это популярная реализация перехватов (рассмотренных выше), объединяющая все pre/post функции в (промежуточный) конвейер, чтобы «обеспечить максимальную гибкость».
Как правило, этот паттерн реализуется тем или иным образом при помощи async.waterfall [26], или async.auto [27], или последовательности обещаний [28], причем может не просто управлять потоком выполнения, но и обеспечивать расширяемость той или иной части вашего приложения.
Пример: Async
async.waterfall([
function(callback){
callback(null, 'one', 'two');
},
function(arg1, arg2, callback){
callback(null, 'three');
}
]};
Черты конвейера есть и у другого популярного компонента Node.js. Как вы уже догадались, речь о так называемых потоках [29], а на что поток, если его нельзя конвейеризовать [30]? Тогда как промежуточный код и цепочки функций вообще – универсальное решение для управления потоком выполнения и расширяемостью, потоки лучше подходят для обработки передаваемых данных в форме байтов или объектов.
Пример: потоки
fs.createReadStream("data.gz")
.pipe(zlib.createGunzip())
.pipe(through(function write(data) {
//... доводим данные до совершенства ...
this.queue(data);
})
// Записываем в файл
.pipe(fs.createWriteStream("out.txt"));
Выводы
Мы убедились, что по природе своей Node.js стимулирует разработчиков использовать определенные паттерны и повторяющиеся приемы. Мы рассмотрели некоторые из них и показали, как они позволяют эффективно решать распространенные проблемы, если применяются правильно. Кроме того, мы убедились, насколько по-разному может выглядеть паттерн в зависимости от реализации.
Автор: Издательский дом «Питер»
Источник [31]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/113456
Ссылки в тексте:
[1] Node.js Design Patterns: http://www.amazon.com/Node-js-Design-Patterns-Mario-Casciaro/dp/1783287314/
[2] паттернов проектирования: http://www.piter.com/collection/starye-tovary/product/priemy-obektno-orientirovannogo-proektirovaniya
[3] антипаттерны: https://ru.wikipedia.org/wiki/%D0%90%D0%BD%D1%82%D0%B8%D0%BF%D0%B0%D1%82%D1%82%D0%B5%D1%80%D0%BD
[4] инкапсуляция: https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BA%D1%80%D1%8B%D1%82%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[5] объектному разврату: https://en.wikipedia.org/wiki/Object_orgy
[6] Наблюдатель: https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
[7] Одиночка: https://ru.wikipedia.org/wiki/%D0%9E%D0%B4%D0%B8%D0%BD%D0%BE%D1%87%D0%BA%D0%B0_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
[8] require-all: https://www.npmjs.com/package/require-all
[9] require-many: https://www.npmjs.com/package/require-many
[10] require-tree: https://www.npmjs.com/package/require-tree
[11] require-namespace: https://www.npmjs.com/package/require-namespace
[12] require-dir: https://www.npmjs.com/package/require-dir
[13] require-directory: https://www.npmjs.com/package/require-directory
[14] require-fu: https://www.npmjs.com/package/require-fu
[15] Фасад: https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D1%81%D0%B0%D0%B4_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
[16] God object: https://ru.wikipedia.org/wiki/%D0%91%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82
[17] Broadway: https://github.com/indexzero/broadway
[18] обезьяньим латанием: https://ru.wikipedia.org/wiki/Monkey_patch
[19] Mongoose: https://github.com/Automattic/mongoose
[20] масса подобных модулей: https://www.npmjs.com/search?q=hooks
[21] npm: https://www.npmjs.com/search?q=aop
[22] срезу: https://en.wikipedia.org/wiki/Pointcut
[23] Connect: https://github.com/senchalabs/connect#readme
[24] Express: http://expressjs.com/
[25] Hooks: https://github.com/bnoguchi/hooks-js/
[26] async.waterfall: https://github.com/caolan/async#waterfall
[27] async.auto: https://github.com/caolan/async#auto
[28] последовательности обещаний: https://github.com/kriskowal/q#sequences
[29] потоках: https://nodejs.org/api/stream.html
[30] конвейеризовать: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
[31] Источник: https://habrahabr.ru/post/278017/
Нажмите здесь для печати.