Изучив довольно много приложений на nodeJS, я заметил, что почему-то практически нигде не используют паттерн MVC так, как это сейчас принято в большинстве PHP-фреймворков, хотя, эта модель мне кажется очень удобной и затраты на ее создание(как я думал в начале довольно серьезные) принесут свои плоды.
Реальный проект
Передо мной поставили задачу — реализовать сервер приложения «игровые автоматы» на node.js, казлось бы — довольно просто. Мне досталось довольно много кода от человека, который занимался этим раньше. В качестве БД используется Redis. Структура приложения выглядела примерно так:
-root
—application.js
—config.js
—constants.js
—import_data.js
—slots_module.js
—user_activity.js
—social.js
—modules/
—тут лежат модули для node.js
Довольно привычная для ноды структура, не так ли? :=)
Но с ростом требований к приложению, и количества всевозможного функционала, как и следовало ожидать его это приложение стало очень трудно поддерживать. Application и config разрослись на 5000 строк каждый, стало много дублирования кода и прочие прелести, стало практически невозможно определить где что находиться, не воспользовавшись поиском по проекту.
И вот, наконец, мы подошли к главному. Накипело. Я решил сделать капитальный рефакторинг и реорганизацию приложения. Первом делом, я вспомнил о том, что есть такая штука, как Object Relational Mapping(ORM) и к моему удивлению я нашел довольно неплохую реализацию ORM для NodeJS и Redis. Это послужило прекрасным толчком к использованию привычной мне архитектуры. Модуль nohm позволяет довольно просто описать модели, их свойства и методы, что позволяет сократить код, сделать его более структурированным и красивым. Вот простой пример того, описания и использование модели пользователя (User.js)
/** * User: hlogeon * Date: 31.07.13 * Time: 23:36 * TODO: continue creating this * read http://maritz.github.io/nohm/ */ var nohm = require('nohm').Nohm; var redis = require('redis').createClient(); nohm.setClient(redis); nohm.model('User', { properties: { balance: { type: "integer", defaultValue: 0, index: false }, ad_id: { type: "string", index: true }, bonus_games_pending: { type: "boolean", index: false }, chips: { type: "integer", defaultValue: 0 }, source: { type: "string" }, spins_count: { type: "integer", defaultValue: 0 }, mute: { type: "boolean", defaultValue: false }, sound: { type: "boolean", defaultValue: false }, charges_base: { type: "boolean", defaultValue: false }, parent_ref: { type: "string", index: true }, sid: { type: "string", index: true }, bonus_to: { type: "integer", defaultValue: 0 }, points_count: { type: "integer" }, parent_id:{ type: "string", index: true }, invitation_accepted: { type: "string" }, ref_type: { type: "string", index: true }, spins_temporary: { type: "integer" }, enter_date: { type: "integer" }, free_spins: { type: "integer" }, screen: { type: "string" }, last_game: { type: "string" }, startOffer: { type: "boolean", index: true }, last_activity: { type: "integer" }, win_turn: { type: "integer" }, double_game_pending: { type: "integer" }, level: { type: "integer", index: true }, last_spin: { type: "integer" }, uid: { type: "string", index: true }, status: { type: "string" }, bonus_games_temporary: { type: "integer", defaultValue: 0 }, browser: { type: "string" }, builded: { type: string, } }, methods: { getContryFlag: function () { return 'http://example.com/flag_'+this.p('country')+'.png'; }, updateBalance: function (value){ var balance = this.p('balance'); this.p('balance', balance+value); this.save(); }, updateChips: function(value){ var chips = this.p("chips"); this.p("chips", chips+value); this.save(); }, incrSpins: function(){ var spins = this.p('spins_count'); this.p('spins_count', spins+1); this.save(); }, swichMute: function(){ var mute = this.p('mute'); this.p('mute', !mute); this.save(); }, swichSound: function(){ var sound = this.p('sound'); this.p('sound', !sound); this.save(); }, setPointsCount: function (value){ this.p('points_count', value); this.save(); return value; }, incrPointsCount: function(){ var count = this.p('points_count'); this.p('points_count', count+1); this.save(); }, incrSpinsTmp: function(){ var tmp = this.p('spins_temporary'); this.p('spins_temporary', tmp+1); this.save(); }, incrFreeSpins: function(){ var spins = this.p('free_spins'); this.p('free_spins', spins+1); this.save(); }, incrLevel: function(){ var level = this.p('level'); this.p('level', level+1); this.save(); return this.p('level'); } } }); var user = nohm.factory('User'); exports.user = user;
Пример использования:
var user = require('./UserTest').user; app.get('/', function (req, res) { var activeUser = nohm.factory('User'); activeUser.save(function(errs){ if(errs){ res.json(errs); } else{ res.json(activeUser.allProperties()); } }); app.get('/findUser', function (req, res) { var id = req.body.id; user.load(id, function(err, aUser){ if(err){ res.json(err); } else{ res.json(aUser.allProperties); } }) });
Согласитесь, это куда проще постоянных redis.hgetall() тем более, что теперь мы можем определить методы пользователя в модели и даже Связи(Relations).
Благодаря такому подходу я разбил приложение на модули и новая структура выглядит так:
-root
—application.js
—constants.js
—config.js
—models/
—User.js
—Slotmachine.js
—Event.js
—helpers/
—Social.js
Хоть файлов стало и немного больше, зато, поддержка кода существенно упростилось, число строчек существенно снизилось, а читаемость возросла просто невероятно! Теперь мне не приходится распутывать лапшу из callback-функций 10 уровня вложенности. Все прозрачно, просто и понятно.
Надеюсь, кому-то будет полезна эта маленькая статейка от новичка в nodejs. Я буду очень благодарен за критику!
ссылка на оригинал статьи http://habrahabr.ru/post/188630/
Добавить комментарий