NodeJS is beautiful, modular, object or make it that way with redis and nohm

Recently, there has been a lot of hype in the IT community around server-side JavaScript, in particular, NodeJS, however, oddly enough, it turned out to be quite difficult to find information on how to write modular, object code. What I mean? The fact is, I’ve been familiar with js recently, before that I wrote small Java applications, and in my free time I write the server side of an online game in PHP and, as you would expect, like many beginning JS programmers, I was very unusual instead of object-oriented use the so-called prototype-oriented programming. Moreover, JavaScript introduces a lot of confusion even in this matter with its Object.prototype and __proto__. The first thing that occurred to me, like many other developers, was to make my implementation of the “familiar OOP”. After a little thought, I decided that it’s just no reason if we are working with nodeJS. For my, albeit short-lived practice, I was not able to meet a task that would require a real OO approach, I mean the real need for inheritance, polymorphism, and even more encapsulation (of course, all this is necessary, but to the extent that js provides).

Having studied quite a few applications on nodeJS, I noticed that for some reason they almost never use the MVC pattern as it is now accepted in most PHP frameworks, although this model seems to me very convenient and the cost of creating it (as I thought in at the beginning quite serious) will bear fruit.

Real project The
task was set for me - to implement the gaming machine application server on node.js, it would seem - quite simple. I got a lot of code from the person who did this before. As a database, Redis is used. The structure of the application looked something like this:
-root
--application.js
--config.js
--constants.js
--import_data.js
--slots_module.js
--user_activity.js
--social.js
--node_modules /
--- here are the modules for node.js The

structure is quite familiar to the node, isn’t it? : =)

But with the growing requirements for the application, and the amount of all kinds of functionality, as one would expect it, this application has become very difficult to maintain. Application and config grew to 5,000 lines each, there was a lot of code duplication and other delights, it became almost impossible to determine where what to find without using the search for the project.

And finally, we came to the main point. Boiling up. I decided to do a thorough refactoring and reorganization of the application. First of all, I remembered that there is such a thing as Object Relational Mapping (ORM) and, to my surprise, I found a pretty good implementation of ORM for NodeJS and Redis. This served as a great impetus to the use of architecture familiar to me. The nohm module makes it fairly simple to describe models, their properties and methods, which allows us to reduce the code and make it more structured and beautiful. Here is a simple example of describing and using a user model (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;


Usage example:

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{
//Если все нормально, давайте вызовем какой-нибудь метод, а потом все свойства пользователя!
                console.log(aUser.getCountryFlag());
                res.json(aUser.allProperties);
            }
        })
    });


Agree, this is much easier than constant redis.hgetall (), especially since now we can define user methods in the model and even Relations.

Thanks to this approach, I divided the application into modules and the new structure looks like this:
-root
--application.js
--constants.js
--config.js
--models /
--- User.js
--- Slotmachine.js
--- Event.js
--helpers /
--- Social.js
--node_modules /

Although there are a few more files, the code support has been greatly simplified, the number of lines has decreased significantly, and readability has increased simply incredibly! Now I don’t have to unravel noodles from callback functions of level 10 of nesting. Everything is transparent, simple and clear.

I hope someone will find this little article from a newbie in nodejs useful. I will be very grateful for the criticism!

Also popular now: