NodeJS красивый, модульный, объектный или делаем его таким с помощью redis и nohm

от автора

В последнее время в IT-сообществе довольно много шумихи вокруг серверного JavaScript, в частности — NodeJS, однако, как это ни странно, оказалось довольно сложно найти информацию о том, как писать модульный, объектный код. Что я имею ввиду? Дело в том, что с js я знаком совсем недавно, до этого писал небольшие приложения на Java, а в свободное время пишу серверную часть онлайн-игры на PHP и, как и следовало ожидать, как и многим начинающим программистам на JS мне было очень непривычно вместо объектно-ориентированного использовать так называемое прототипно-ориентированное программирование. Тем более, JavaScript вносит достаточно много путаницы даже в это дело со своими Object.prototype и __proto__. Первое, что пришло мне в голову, как и многим другим разработчикам — сделать свою реализацию «привычного ООП», сделал. Немного подумав я решил, что это просто незачем, если мы работаем с nodeJS. За мою, хоть и недолгую практику, мне не довелось встретить задачу, которая бы требовала настоящего ОО подхода, я имею ввиду реальную необходимость наследования, полиморфизма и тем более инкапсуляции (конечно, все это нужно, но в той степени, которую js предоставляет).

Изучив довольно много приложений на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *