Построение модульной системы на основе Nodejs

Основные требования


• легкость подключения модулей
• стандартная структура URL
• многоязычность
• автоматическое принятие изменений
• использование возможностей многопроцессорной системы
Система построена на базе Express. Для облегчения написания кода используется модуль wait.for

Файловая структура


  • root
    • routes
      • mod_api
        • test.js
      • api.js
    • views
    • public
    • app.js
    • Server.js


Демонизация сервера под линукс


В нашем проекте был использован модуль forever.
-w — позволяет изменять модули без прямой перезагрузки сервера. Forever следит за изменениями и перегружает сервер по надобности.
-l ведет в пустоту, так как было решено, что два лога — это черезчур.
forever start -a -w -l /dev/null -o out.log -e err.log Server.js

Использование возможностей многопроцессорной системы


Запуск множества процессов посредством модуля «cluster» для распределения нагрузки между ядрами.
//файл Server.js
var cluster = require('cluster');

var workerCount = require('os').cpus().length;

cluster.setupMaster({ exec: "app.js" });

// Fork workers.
for (var i = 0; i < workerCount ; i++)cluster.fork()

Подключения модулей


Легкость подключения модулей достигается двумя включениями.
Автоматическое подключение всех routes

// part of app.js
// путь к routes относительно запускаемого файла
var routesPath = path.join(__dirname, 'routes');

//лист всех файлов в директории
var routeList = fs.readdirSync(routesPath);
for(var i in routeList){
    var filePath = path.join(routesPath,routeList[i]);
    if(fs.statSync(filePath).isFile() && path.extname(routeList[i])=='.js')
        require(filePath)(app); // инициация путей
}

Подключение модуля и вызов функции

Routes дефинируются отдельно. в каждом файле, чем так-же достигается унификация и простота подключения routes для конечного разработчика.
//файл api.js

module.exports = function (app) {
    app.get('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.GET);});
    app.post('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.POST);});
    app.delete('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.DELETE);});
    app.put('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.PUT);});
};


function action(req, res, method) {

    //проверка языка
    var lang = req.params.lang.toUpperCase();

    if (global.conf.AVAILABLE_LANGUAGES.indexOf(lang) > -1) {
      // чистка имени модуля 
	var mod = req.params.mod.replace(/[^a-zA-z0-9]+/g,'');
      // чистка имени функции
	var action = req.params.action.replace(/[^a-zA-z0-9]+/g,'');

      // проверка существования модуля  
	fs.exists(path.resolve(__dirname, './mod_api/'+mod+".js"),function(ok){
            if(ok){
		   // вызов модуля
                var startMode = require('./mod_api/'+mod);
                try{
		    // вызов функции с префиксом pub_
                    wait.launchFiber(startMode['pub_'+action], req, res, lang, method);
                }catch(err){
                    res.status(405).pj(405,err.message,"Method Not Allowed");
                }
            }else{
                res.status(503).pj(503,null,'Service Unavailable ');
            }
        });
    }
    else{
        res.status(400).pj(400,null,'Not supported language');
    }
}


Имплементация конечной функции


Все функции с префиксом pub_ являются публичными, все остальное приватное.
// file routes/mod_api/test.js
exports.pub_Start = function(req, res, lang, method){   
   res.pj(0,(method===global.conf.METHODS.POST)?"POST":"GET","SUCCESS ");
}

Унификация вывода


//part app.js
http.ServerResponse.prototype.pj = function(status,data,message){
    try {
        this.json({STATUS:status,CONTENT:data,MESSAGE:message});
    }catch(e) {
        console.error(e);
        this.json({STATUS:999,CONTENT:null,MESSAGE:'parse response error'});}
};


Ну и в завершении вызов функции: api.codevit.net/api/test/en/Start
(я очень надеюсь, что сервер не обвалиться, это мой тестовый сервер)

Если кому-то будет интересно, могу выложить скелет на github.
О синтаксических ошибках прошу писать в приват.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 16
  • 0
    //лист всех файлов в директории
    Можно подключать директории с помощью require-dir. Есть несколько пакетов, которые справляются с этой задачей, например require-dir.
    • 0
      можно сделать и так, мы пока не решили, стоит ли это делать из-за пяти сторочек кода.
      • 0
        В инфрастуктуре ноды/npm очень много пакетов, которые состоят из пяти строчек, но, тем не менее, делают жизнь лучше. Я рекомендую узнавать про такие штуки и пользоваться ими, потому что они перекладывают часть технических проблем на пакеты, позволяя сосредоточиться на значимых вещах.

        Лично я не пользуюсь require-dir, и этим подходом вообще. Я предпочитаю создавать файл index.js и вручную в него подключать все субмодули. Снаружи можно просто подключать директорию, в этом случае читается индексный файл. Это позволяет прогонять код через бандлеры CommonJS. Да и явное лучше неявного.
    • 0
      А не думали методы (get, post,...) в название функции вынести? Например, pub_get_start(). Насколько я понял, сейчас в каждой функции нужно проверять, как пришел запрос?
      • –2
        Думали, но решили, что плодить сущности не разумно. Не всегда требуются все функции, а так можно уже внутри сделать разделение логики.
      • 0
        Не совсем понятно, ради чего топик-то.
        Про то, как при поступлении на вход URL'а проверить, есть ли соответствующий файл и сделать ему require? Так в этом ничего такого.
        Про то, что хорошо бы использовать унифицированное api для однотипных задач? Ну так и это очевидно.

        В общем, странно как-то.

        P.S. А что за HTTP status code такой, 999-й? Имхо, правильнее было бы 501-й выставлять.
        • –1
          Идея была создать систему для конечных разработчиков, не имеющих никаких прав и доступов, кроме ФТП. Создал новый файл модуля, загрузил его на сервер. профит.

          P.S. раньше было 666, но шеф перевернуть заставил.
        • +1
          т.е. на каждый запрос вы проверяете наличие файла, потом загружаете его и выполняете действия в фибере? Вы проводили бенчмарки?
          А uppercase STATUS, MESSAGE — это что такое? Дикость в уралах (uppercase) вида en/Start — это тоже нормально видимо? Знаете я бы посоветовал никому не делать как вы. Простите конечно, но такое ощущение что вы никогда не работали с нодой.
          • –3
            1. с нодой работаю давно и плотно, и как вы писали в своем посте о ноде это лично мое правило, я никому его не навязываю

            2. бенчмарки — нет не проводили, но хочу заметить, что механизм импорта файлов в ноде, достаточно сильно отличается от того же пхп, из-за чего и используется forever -w, чтобы не перегружать сервер каждый раз ручками. Система рабочая и пока проблем в Индонезии, даже на слабых серверах замечено небыло, не смотря на то, что используется в социальной сети и для выдачи статистики.

            3. у нас есть определенные корпоративные правила, из которых и следует «uppercase » и «Дикость в уралах». Хорошо это или плохо — вопрос воспитания.

            • +1
              Это доказывает, насколько Вы плохо знаете node.js и его механику. Зачем проверять и загружать каждый модуль, при каждом запросе? Ответ очевиден, Вы пришли с (PHP/Ruby/Python)!
              • 0
                Вобщем то с Ява и С#. А вы не задумывались, что у меня для этого могла быть причина? Если вам все очевидно, то я надеюсь,
                . она вам так же ясно видима.
                • 0
                  Причина была одна, динамически подгружать модули без перезапуска приложения. Но Node.js при require(once) хранит модуль в памяти и последующие вызовы его не имеют смысла. Сбор мусора в V8 не сможет нормально работать при таком подходе, когда Вы динамически изменяете приложение в памяти, из этого последуют страшные утечки памяти при каждом запросе. Помните, что Node.js приложение при каждом запросе не убивает себя и не исполняется сверху вниз, более того оно запускается «1 раз и на всю жизнь»
                  • 0
                    прошу обратиться к meettya по поводу кеширования require в Ноде, если мне не верите.
          • +1
            Ох. Вы только не обижайтесь, идея здравая, но её качество вам бы подкачать.

            var workerCount = require('os').cpus().length;
            

            не делайте так, процы с гипертрейдингом (или как там называется эта маркетинговая шляпа) наивно отдают х2 ядер, что по факто вранье и толком не работает. Вынесите в конфиг.

            Статус 999 — детский сад. Есть rfc на статусы ответов, возьмите подходящий код.

            Забудьте про синхронные вызовы. ИМХО их использование нельзя оправдать ничем. Ну разве что эмм. невысоким уровнем разработчика. Очень невысоким.

            Забудьте про global. Или реквайрте явно, или передавайте параметром. Глобали вообще нет оправданий, даже если это написал Ризинг. Но он так не сделает.

            Именование функций местами сбивает с толку. Не пишут ok, пишут isOk, ибо булен-флаг.

            И последнее (на что у меня хватило сил) — реквайр ноды устроен хитро и конечно же кеширует последующие require, но идея делать его на каждый запрос вызывает оторопь.

            В общем у вас есть место для улучшений.
            • 0
              Спасибо за развернутый коментарий.

              «реквайр ноды устроен хитро и конечно же кеширует последующие require, но идея делать его на каждый запрос вызывает оторопь.» — как я уже говорил, я работаю с группой Индонезийцев и они гораздо хуже «обезьяны с гранатой», эта проверка позволила исключить вылет системы и обеспечить стабильную работу даже, если они удалили, переименовали или добавили новый суб-модуль.

              синхронный вызов тут только один и он вызывается при старте ноды, по поводу wait.launchFiber, я не нашел возможности сделать это асинхронно.

              Не пишут ok, пишут isOk, ибо булен-флаг. — мой косяк
              ,
              global — я храню там глобальный конфиг, с подключениями к базам данных, доступные языки, общие константы. Мой опыт голосует за разумное использование этой возможности.

              А вобщем за идею загнать колличество процов в конфиг — спасибо, но есть один нюанс, если Индонезийцы будут переносить систему на другой сервер, то с гарантией 90%, они забудут это изменить. Буду искать реальный способ определить колличество доступных ядер. Хотя пока мне везло с серверами.

              • 0
                Насчет синхронных функций Вы не правы. Почему при загрузке приложения я не могу делать так:

                if (fs.existsSync(__events))
                  fs.readdirSync(__events).forEach(function (eventName) {
                    if (!jsFileRegexp.test(eventName)) return;
                    debug('load event', eventName);
                    require(path.join(__events, eventName));
                  });

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое