📜 ⬆️ ⬇️

NodeJS is beautiful, modular, object or make it so with the help of redis and nohm

Recently, in the IT community, quite a lot of hype 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 that I was familiar with js quite recently, before I wrote small Java applications, and in my spare time I write the server part of an online game in PHP and, as was to be expected, like many novice JS programmers, I was very unusual instead of object-oriented use of so-called prototype-oriented programming. Moreover, JavaScript brings a lot of confusion even in this case with its Object.prototype and __proto__. The first thing that occurred to me, like many other developers, was to make my own implementation of the “usual OOP”. A little thought, I decided that this is simply no need if we work 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 especially encapsulation (of course, all this is necessary, but to the extent that js provides).

Having studied quite a lot of applications on nodeJS, I noticed that for some reason practically nowhere 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 early enough serious) will bear fruit.

Real project
I was assigned the task of implementing the “slot machines” application server on node.js, it would seem - quite simple. I got quite a lot of code from a person who has done this before. Redis is used as a database. The structure of the application looked like this:
-root
--application.js
--config.js
--constants.js
--import_data.js
--slots_module.js
--user_activity.js
--social.js
--node_modules /
--- there are modules for node.js

A pretty familiar structure for a node, isn't it? : =)
')
But with the growth of requirements for the application, and the number of various functionalities, as one would expect it, it became very difficult to maintain this application. Application and config grew by 5000 lines each, it became a lot of duplication of code and other amenities, it became almost impossible to determine where to be, without using the project search.

And finally, we come to the main point. Boiling hot. I decided to do a major 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 ORM implementation for NodeJS and Redis. This was a great impetus to the use of my usual architecture. The nohm module allows you to quite simply describe the models, their properties and methods, which allows you to reduce the code, make it more structured and beautiful. Here is a simple example of how to describe and use a user model (User.js)
/** * User: hlogeon * Date: 31.07.13 * Time: 23:36 * TODO: continue creating this * read http://maritz.imtqy.com/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); } }) }); 


You must admit that this is much simpler than the permanent redis.hgetall (), especially since now we can define user methods in the model and even in Relations.

Thanks to this approach, I broke 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 the files have become a bit larger, the code support has become much simpler, the number of lines has decreased significantly, and the readability has increased is incredible! Now I don’t have to unravel noodles from callback functions of level 10 nesting. Everything is transparent, simple and clear.

I hope someone will benefit from this little article from a beginner in nodejs. I will be very grateful for the criticism!

Source: https://habr.com/ru/post/188630/


All Articles