Understanding the value of Node Modules

“Separation of concerns” is, perhaps, the most important topic in software development.  Understanding how we can properly separate disparate parts of our code so they logically make sense while reducing our coupling to other areas is an essential task in modern software development.  Within Node.js logic is generally separated using “modules”.

Those who use Node.js are very familiar with “modules” as they form the backbone to Node development, mostly via npm (Node Package Manager).  In these cases, the “modules” expand the functionality of Node and make difficult tasks easier, great examples include express and  request.  In fact, Node itself is built in a module concept, enabling developers to bring in only what they need. Node also enables you to write custom modules, which are a great way to build solutions which are testable and maintainable.

To begin, this is some code I wrote a while back, it works but its not written very well, and it certainly isnt taking advantage of Node.js provides us:

// prototype functions
String.prototype.asWeekNumber = function() {
    var result = this.match(/\d\d?/);
    return parseInt(result[0].toString());
};

// actual job code
function LoadSchedule() {
     var request = require("request");
     request({
          url: "some/url/returning.json",
          json: true
     }, function(error, response, body) {
          if (!error && response.statusCode == 200) {
               var body = JSON.parse(body.replace(/,+/g, ','));
               var key = Object.keys(body)[0];

                    body[key].forEach(function(item) {
             if (item.length == 10) {
                  var game = createGame(item);
                  addGame(game);
             }
        });
          }
     });
}

function createGame(array) {
  return {
     gameDay: array[0],
     gameTime: array[1],
     gameState: 'P',
     awayTeam: array[3],
     awayTeamScore: 0,
     homeTeam: array[5],
     homeTeamScore: 0,
     weekNumber: array[8].asWeekNumber(),
     year: parseInt(array[9])
  };
}

function addGame(game) {
     // doing some SQL Server stuff
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

For the most part, this looks like most of the JavaScript you see every day.  It is purely functional and defines many random functions.  Most of these cannot be reused without copying which is an obvious violation of DRY (Dont Repeat Yourself).  So what can we do?

Thinking about this from a separation of concerns perspective, we probably want to move the code that is making the request and parsing the return into a module.  This way, the LoadSchedule method isnt aware of what is being done just that it IS being done.

Here is the updated code for LoadSchedule following the refactor

function LoadSchedule() {
     var spSchedule = require("score_predict_schedule");
     spSchedule.getCurrentSchedule({
          success: function(games) {
               games.forEach(function(game) {
         if (game.weekType != "PRE") {
              // do not load the preseason
              addGame(game);
         }
               });
          },
          fail: function(error) {
               console.log(error);
          }
     });
}

function addGame(game) {
     // do some SQL Server stuff here
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As you can see, it is A LOT cleaner.  The important line is require(“score_predict_schedule”).  This bring in our module.  Because we have it located under node_modules we dont even need to specify a path.  If we look at the source code inside:

// parser reference
var parser = require("./week_parser");

// Module for handling schedule retrieval
module.exports = {
     _createGameFromType10: function(array) {
          var weekInfo = parser.parseWeekInformationFromString(array[8]);
               // return the game parsed from a Type 10 result
          },

     _createGameFromType8: function(array) {
          var weekInfo = parser.parseWeekInformationFromString(array[6]);
          // return the game parsed from a Type 8 result
     },

     getCurrentSchedule: function(handler) {
          var request = require("request");
          var gameList = [];
          var self = this;

          request({
               url: "http://some/url/returning.json",
               json: true
          }, function(error, response, body) {
               if (handler === undefined) {
                    console.log("You must define a handler");
                    return;
               }

               if (!error && response.statusCode == 200) {
                    var body = JSON.parse(body.replace(/,+/g, ','));
                    var key = Object.keys(body)[0];
                    body[key].forEach(function(item) {
                        if (item.length == 10) {
                           gameList.push(self._createGameFromType10(item));
                        } 

                        if (item.length == 8) {
                          gameList.push(self._createGameFromType8(item));
                        }
                   });

                   handler.success(gameList);
               }
               else {
                    handler.fail(error);
               }
          });
     }
};

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

We see that this has allowed us to break up different parsing branches and thereby allow our caller to not worry about HOW things get parsed, just received the list so it can perform the next step.

Something interesting here is the definition of parser.  I was able to also refactor the logic which some specialized parsing into a separate sub module within our custom module.  That being the case, I could reference it directly, sans the .js extension.  Its definition is identical to this.

The advantage with this approach is now I can more easily write unit tests and bootstrap files to test this code.  Before, I would have had to load the entire file and call my method.  Seems easy, but it does make testing more difficult in the long run.  In addition, if I ever had to do a similar operation I would have had to duplicate large chunks of that code.  With this new approach, I can just call require and be done with it.

You will find an approach like this in most software development projects.  The rule is “verify all the smaller pieces work so when you put them together into something bigger, its more likely to ‘just work’”.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s