Use dependency injection!

Take a nodejs app, which has :

Those both will most likely be instancianted once in your application. A database pool might have a more complicated behavior, but at the end it’ll has the same result: a pool instance to work with.

Assuming the configuration reads a file, do some async job (creating a directory structure for example) and add some default values. This is a small implementation:

// config.js
'use strict';
var Promise = require('bluebird') 
var fs = Promise.promisifyAll(require('fs'))

module.exports = function(config_path) {
  return fs.readFileAsync(config_path)
  .then(function(str) {
    let config = require('yamljs').parse(str)
    config.redis = config.redis || '127.0.0.1:6379'
    config.redis_prefix = config.redis_prefix || 'foo'
    return config
  })
  .catch(function(e) { console.error('No configuration in %s', config_path) })
}

That’s nice and easy, just taking a path, reading and parsing the configuration.

Let’s add some models, or interfaces to our redis instance to do some stuff. What we need is a redis client and our configuration to work properly.

Now, I’ve seen things like this over and over again (this is a bad example):

// models/message.js
'use strict';
var Promise = require('bluebird') 
var redis = require('redis')
Promise.promisifyAll(redis.RedisClient.prototype);

module.exports = {
  add: function(key, value) {
    //The code speaks for itself, notice the callback hell?
    require('config.js')('config.yml').then(function(config) {
      return redis.createClient()
      .then(function(client) {
        //yup, we need the client 
        return client.hsetAsync(config.prefix, key, value)
        .then(function(n) {
          if(n == 1) {
            //here too \o/
            return client.hgetAsync(config.prefix, key)
          } else { 
            return Promise.reject('Not added')
          }
        })
      })
    }) 
  }
}  

Check the comments, we had to use a promise inside the redis.createClient promise just to use the client variable. Same goes for the config. Here, if the path changes, you’ll have to change every file you use config.js in. It sucks.

We can definitly do something much better here!

To do so, I’ll use some dependency injection. First for the redis client:

// redis.js
var Promise = require('bluebird') 
var redis = require('redis')
Promise.promisifyAll(redis.RedisClient.prototype);

//config will be injected here
module.exports = function(config) {
 var client = redis.createClient(config.redis)
 client.onAsync('ready')
 .thenReturn(Promise.resolve(client));
}

Then our models/message.js can be written like this:

// models/message.js
module.exports = function(config, redisClient) {
  return {
    add: function(key, value) {
      return client.hsetAsync(config.prefix, key, value)
      .then(function(n) {
        if(n == 1) {
          return client.hgetAsync(config.prefix, key)
        } else { 
          return Promise.reject('Not added')
        }
      })
    }
  }
}

Cleaner right? Now put those together:

// index.js
//this could be in a separated file, adding the web server etc.
function launchApp(config, redisClient) {
  var messages = require('models/message.js')(config, redisClient)
  messages.add('foo', 'bar')
}

//main entry point, get config, database and inject the damn thing!
require('config.js')(process.env.MY_CONFIG_PATH)
.then(function(config) {
  require('redis.js')(config).then(function(redisClient) {
    launchApp(config, redisClient)    
  })
})

Here we’re only using one redisClient, but it could be a cache service that operates in redis, memory or memcache according to the configuration. Note that I would personally find this code better with Prototypes.

This way it’s more SoC-compliant, and the code is easier to manage/share/read. Also, you won’t repeat yourself, in comparison to the first example. Think about tests, changing the configuration path, reading your code again in a couple of years…

The above code is untested, it’s just a proof of concept to illustrate a though.