Как стать автором
Обновить

История одного маленького эксперимента с Node.js

Время на прочтение7 мин
Количество просмотров13K
Вот что может получиться если делать приложение на незнакомых технологиях при ограниченном времени.
Эта статья будет интересна скорее всего новичкам ну или просто для развлечения. Будьте осторожны статья полна субъективных оценок и бестолковый рассуждений автора.

Началось все вот с этой статьи и давнего желания поближе познакомиться с Node.js. Упускать такое стечение обстоятельств было нельзя). Что из этого получилось вы найдете под катом.



Итак начнем. Задача

Было решено сделать маленькое и пушистое веб приложение для хостинга формул. Ну то есть приходишь, вводишь формулу, жмешь кнопку, получаешь ссылку на рендер формулы и радуешься. Формулы будем вводить в MathML. Ну и было бы круто еще иметь возможность найти интересующую нас формулу и получить ее рендер или MathML. Важно свести потребление ресурсов к минимуму.

Задача определена, далее архитектура

В результате из всего вышеперечисленного вытекало следующее:
  • Приложение строго разбито на две части клиент и сервер
  • Общение клиента с сервером через “почти” :) RESTfull
  • На сервере Node.js
  • В качестве базы данных MongoDB


Далее обо всем чуть подробнее

Клиент / UI / Браузер

На клиенте уже довольно стандартный AngularJS. На мой взгляд пока нет ничего лучше для разрешения биндинга и построения интерфейса, по моему мнению это основная проблема при написании веб приложений и ангуляр с ней справляется на 5 с плюсом. Помимо ангуляра на клиенте я использовал Typescript, Jadeи Sass.
  • Typescript — возможности относительно проще разбить код на модули ну и конечно же писать типизированный код и использовать человеческие классы ни что не заменит как бы не орали разного рода JS извращенцы.
  • Jade — красиво, модули, функции, минимализм.
  • Sass — из-за возможности побить код на модули, переменных и т.п. мелочей, делающих мою жизнь немного приятней.


Иконки берем Font Awesome, палитру из гугловского material тут и пока обходимся без JQuery.

Сервер

Тут все намного страшнее чем может показаться с первого взгляда. На ноде я никогда ничего не писал и это первый мой блин, который получился не совсем уж и комом.
Первое что меня удивило это количество разнообразных библиотек. Конечно это не джава, но для поставленной задачи я нашёл все что мне могло потребоваться

intel — достаточно удобная и функциональная библиотека для логирования в ноде
config — простой способ использования конфигурационных файлов
mongoose — своего рода хибернейт между нодой и монгой
express — веб фреймворк для ноды. Упрощает и без того не сложный процесс создания веб сервера на ноде
body-parser — обработка POST запросов что следует из названия
Agenda — job scheduling
Libxmljs — работа с XML, что важно без JVM в зависимостях

Качество этих модулей иногда не такое как хотелось бы). Например libxmljs… В убунте не ставится без бубна как то так

cp build/Release/lib.target/xmljs.node build/xmljs.node


На стороне сервера я, также использовал Typescript ровно по тем же причинам что и на клиенте. Но на этой стороне возникли некоторые проблемы… Если на клиенте удалось найти подход при котором код не усложнялся, то в ноде применение Typescript немного усложнило\запутало код, но не на столько чтобы от него отказаться.

mongoose

Это интересная библиотека позволяющая создавать схемы данных, автоматически валидировать, сохранять и читать данные из монги в соответствии со схемой. Это очень полезная и удобная штука. Mongoose упрощает работу с монгой из ноды до неприличия…
К примеру вот так можно сохранить новую формулу в базу
var formula: any = new entity.Formula();
formula.name = req.body.name;
formula.description = req.body.descr;
formula.mathml = req.body.data;
formula.save();


Или найти формулу
entity.Formula.find( {_id: req.params.id} )
.select(bdKey)
.exec( 'find', (err, formula): void => {
    if ( err || (formula.length !== 1) ) {
        res.send( 404 );
    } else {
        res.header("Content-Type", "image/png");
        res.send(formula[0][bdKey], {}, function (err) {});
    }
});


Аgenda

Очередь — это неотъемлемая часть алгоритмов подобных разрабатываемому алгоритму конвертации формул из-за проблем с производительностью. Большой поток запросов на конвертацию может очень быстро убить наше приложение. Самый логичный и простой способ — это очеред.
Стоит отметить что для организации очередей есть несколько библиотек для Node.js. Я же выбрал Аgenda по следующим причинам:
  • Есть возможность сохранять задачи в MongoDB
  • Библиотека позволяет организовать не только очередь в ее привычном понимании но и выполнение зачем по расписанию что является очень приятным дополнением.


Добавить задание в очередь
service.agenda.now( 'process new formula', {fid: fid} );

Зарегистрировать обработчик задания
service.agenda.define('process new formula', (job, done): void => {
	var data = job.attrs.data;
	log.debug( "process new formula: " + data.fid );
	AgendaService.processRendering( data.fid, done );
});


Libxmljs

Важно и даже критично проверить данные решение в запросе. Для валидации данных мне удалось нагуглить достаточную интересные библиотеки express-validator и validator.js позволяющие выполнять ту самую валидацию достаточно эффективно. Но мне нужно было проверять MathML и для этого мне встретилось две библиотеки xsd-schema-validator и Libxmljs. Первая использует Java/SAX а это значит нужно иметь JVM да и с ней что-то не заладилось с первых минут поэтому в результате я использовал Libxmljs. Никакой джавы не нужно для ее работы но вот как раз она принесла баги на который я потратил достаточно много времени.
Для валидация MathML я исполтзовал схемы второй версии, которые раздобыл тут.

Как то так выглядит валидация
fs.readFile('mathml2.xsd', {encoding:'utf-8'}, (err, data): void => {
    try {
        if (err) { process.chdir(cwd); fail(); return }
        var xsdDoc = libxmljs.parseXmlString(data);
        var xmlDoc = libxmljs.parseXmlString(xsd);
        var valid = xmlDoc.validate(xsdDoc);

        log.debug( "xsd validation" + valid );

        process.chdir(cwd);
        if ( valid ) ok(); else fail();
    } catch(e) {
        process.chdir(cwd);
        fail();
    }
});


Рендеринг

Для рендеринга формул используем MathJax в связке с PhantomJS. Не самый быстрый и стабильный вариант, но такую связку получилось реализовать достаточно быстро что в условиях ограниченного времени является неоспоримым преимущество ;).

MongoDB

Для хранения формул используется MongoDB. Хостинг формул предполагает быстрый доступ к ним. По этой причине в монге хранятся не только MathML но и рендер картинок формул (не по причине быстрого доступа а потому что их нужно где-то хранить). Картинки занимают не много место и как раз вписываются в рекомендации хранить их как поле в базе без использовался дополнительных библиотек и фреймворков таких как GridFS. С другой стороны, хранение отрендеренных картинок в монге дает все преимущества хранения данных в базе данных а не в тупую на файловой системе.

Схема данных
module entity.schema
{
    export var IFormula: any = new mongoose.Schema({
        name: {type: String, default: 'Формула'},
        description: {type: String, default: ''},
        created: {type: Date, default: Date.now},
        modified: {type: Date, default: Date.now},
        mathml: {type: String, default: ''},
        png200: {type: Buffer},
        png100: {type: Buffer},
        png50: {type: Buffer},
        png200t: {type: Buffer},
        png100t: {type: Buffer},
        png50t: {type: Buffer},
        ready: {type: Boolean, default: false},
        error: {type: Boolean, default: false}
    });
}


Сборка проекта

Для сборки проекта я использовал Gulp и это был первый и 'странный' опыт… Меня порадовало количество и качество плагинов для него. Первый раз написав сценарий для Gulp после Grunt’a — я получил скрипт, который жил своей жизнью, он делал совсем не то, что я ожидал. Причина всему мой не параллельно ориентированный после Grunt’a мозг. После осознания ошибок я начал переделывать скрипт сборки, но было немного поздно и на текущий момент работа еще не завершена. Сейчас сборка выполняется bash скриптом, который вызывает пару задач Gulpа. Совсем не красиво но работает и переделка почти завершена. Сейчас проблема в основном с компиляцией тайпскрипта, при выбранном подходе (компиляция всего в один файл) не удается поймать окончание процесса компиляции. Тут буду рад помощи.

Хостинг приложения

Приложение я разместил на двух микроинстансах в облаке гугл воспользовавшись щедрым подарком в 300 долларов на эксперименты. Как нельзя кстати. Ну и опять таки двоякое ощущения от использования облака. Во-первых, это очень и очень круто и я остался очень доволен от использования облачных технологий (по кране мере на данном этапе). Ну и, естественно, в бочке меда никак без ложки дегтя. Проблема вся в том что вот так вот с ходу мне не удалось написать адекватный деплоймент скрипт который бы производил установку приложения в инстанс в облаке вместе с установкой модулей ноды. Процедура прекрасно работает при использовании дебиана на локальной виртуалке но в дебиан предлагаемый при создании инстанса в облаке ошибка доступа к файлам при выполнении npm install так как новые файлы в ходе этой процедуры создаются от пользователя а не от рута даже если выполнять команду от его имени. Возможно проблема в кривых руках но сейчас установка производится в два этапа. На первом создаем все модули от имени пользователя а на втором выполняем установку от рута. В целом же облако это добро, особенно если это облако гугл ;)

Ну и чтобы жизнь медом не казалась выбираем домен в зоне РФ. Тут особых проблем не возникло. Единственное что в настройках Nginx нужно указывать punycode имя а не ставить туда русские буквы.

Для моего Nginx это выглядит так
listen       80;
server_name  xn--c1ajpkj4do.xn--p1ai;


Приложение и JSON

В результате получилась очень интересная система в которой данные представляются JSON объектами на всех этапах от интерфейса в браузере, продолжая нодой и заканчивая базой данных. Это мой первый опыт работы с подобной системой и если честно я получил от этого своего рода удовольствие. Это мелочь, когда в вашем приложении нет преобразований данных, но лично для меня эта мелочь дает некоторую прозрачность, которая делает лично мне хорошо :).

Ну и что получилось в итоге

А получилось простое веб приложение позволяющее хостить формулы. При сравнимо невысоких ресурсах железа (два микроинстанса гугловского облака) удалось сделать достаточно простое и устойчивое к нагрузкам приложение.
В целом я остался доволен описанными выше технологиями. Приятно удивило количество и качество библиотек для ноды и легкость написания кода. Неприятный осадок оставили пара багов в библиотеках ноды. Иногда даже казалось все ужасно сырым и недоделанный. Но ошибки оказались решаемы и не отняли уж очень много времени. Кстати говоря на разбор и исправление ошибок (включая одну правку в исходниках) я потратил меньше времени чем при настройке и осмыслении некоторых библиотек в джаве. Не кидайте какашками я прекрасно понимаю разницу в уровне ноды и джавы, я говорю лишь об ощущениях.

Результат
Код

Буду рад конструктивной критике и просто отзывам.
Теги:
Хабы:
+3
Комментарии8

Публикации

Истории

Работа

Ближайшие события