Using CouchDB to store and propagate configuration changes


This is the seventh article from a new series about Node.js. In the previous article we started talking about how you can deliver configuration data to your Node.js processes. In this one we’ll see how we can use CouchDB to deliver configuration changes without needing to restart your processes.

Enjoy!

CouchDB is a key-value store where the values are arbitrary JSON documents. One CouchDB server can have an arbitrary number of databases, each database an arbitrary number of documents, indexes by key. Each document has a revision number: every time one document is updated, the document revision number gets incremented. This can come in handy for clients detecting stale data and conflicts. CouchDB also has some other interesting features:

  • each database has a monotonic sequence number, which increases each time a document gets inserted, updated or deleted.
  • CouchDB stores every version of every document, alongside it’s revision number

Also, a CouchDB database provides a continuous changes feed. Given a sequence number, this feed provides every change done in the database since. If the sequence number is not provided, CouchDB will giev us all the changes the database has gone through since creation.

Since CouchDB stores every version of every document, a database occupies an ever-increasing amount of space. To mitigate this you can frequently compact a database, losing all history.

Given that any document is an arbitrary JSON document and that it provides a changes feed, CouchDB can be used for storing and propagating changes throughout processes. Let’s see how.

Installing and setting up CouchDB

First, to try out the following examples, you will need to have a CouchDB server running on your computer. For that you can head out to the following URL that has links to the most used OSs:

http://docs.couchdb.org/en/1.6.1/install/index.html

Once you have it installed, start CouchDB. By default CouchDB is installed in “Admin party” mode, meaning that you don’t need a username and password to be able to perform any operation.

CouchDB talks HTTP, which means that you can use an HTTP command-line client like curl.

If you don’t have curl installed you can try to install it via your favorite package manager (if you have one) or head out to the official curl download page: http://curl.haxx.se/download.html

Using curl we can now query our server:

$ curl http://127.0.0.1:5984  
{"couchdb":"Welcome","uuid":"ac754b216ef3c66aabb88276beb105dd","version":"1.6.1","vendor":{"version":"1.6.1-1","name":"Homebrew"}}

Here we can see that CouchDB replies with a welcome JSON document.

Creating a configuration database

Now that we have a server, we need to create a database where we will store our configuration documents. We’ll aptly call this database config and create it using curl:

$ curl -X PUT http://127.0.0.1:5984/config  
{"ok":true}

And that’s all we will need, really. Now we have a database where we can store our configuration.

Let’s say that we want to store some configuration data related to an e-mail sending service. We can then create the respective configuration document:

{  
  "\_id": "email.send",  
  "user": "SOME.USER.ID",  
  "token": "A.SECRET.ACCESS.TOKEN",  
  "defaultSender": "info@example.com"  
}

To create this document we could use curl, but an easier way is to use the administration interface provided by CouchDB, available at http://127.0.0.1:5984/_utils/:

You can now click on the config database and click on the New Document button. You can then paste the configuration document above and click on the Save Document button.

When starting a document creation through Futon, CouchDB creates a random document ID for you, but in our case we want to override that value with email.send, making this the effective document ID.

You can now query the configuration value using curl:

$ curl http://127.0.0.1:5984/config/email.send  
{"\_id":"email.send","\_rev":"1-25606b60a70485c21ff7d4b875ad891e","user":"SOME.USER.ID","token":"A.SECRET.ACCESS.TOKEN","defaultSender":"info@example.com"}

Getting the changes

Now that we have a way of updating the configuration, we need a way to distribute the configuration throughout our Node processes. For that we will use the follow package, that we can set on a new package.json manifest for our new app:

package.json:

{  
  "name": "test-app",  
  "version": "0.1.0",  
  "dependencies": {  
    "follow": "^0.11.4"  
  }  
}

We should now install it:

$ npm install  
follow@0.11.4 node\_modules/follow    
├── debug@0.7.4  
├── browser-request@0.3.3  
└── request@2.51.0 (caseless@0.8.0, json-stringify-safe@5.0.0, forever-agent@0.5.2, aws-sign2@0.5.0, stringstream@0.0.4, oauth-sign@0.5.0, tunnel-agent@0.4.0, node-uuid@1.4.2, qs@2.3.3, mime-types@1.0.2, combined-stream@0.0.7, tough-cookie@0.12.1, hawk@1.1.1, form-data@0.2.0, http-signature@0.10.0, bl@0.9.3)

Now we will create a singleton config module which:

  • exports the configuration and
  • is responsible for updating itself when the value in CouchDB changes

config.js:

var EventEmitter = require('events').EventEmitter;    
var follow = require('follow');
var config = module.exports = new EventEmitter();
var source = process.env.CONFIG\_SOURCE\_URL || 'http://127.0.0.1:5984/config';
var seq = 0;
feed = new follow.Feed({    
  db: source,  
  since: seq,  
  include\_docs: true  
});
feed.on('change', onConfigChange);
feed.follow();
config.stop = function() {    
  feed.stop();  
};
function onConfigChange(change) {    
  var id = change.id;  
  var doc;
  if (change.deleted) {  
    delete config\[id\];  
  }  
  else {  
    doc = change.doc;  
    delete doc.\_id;  
    delete doc.\_rev;
    config\[id\] = doc;  
  }
  config.emit('change', id, doc);
  console.log('config.%s after change:', id, doc);  
}

Let’s analyse this module in parts.

First, we require some modules that we will need later:

var EventEmitter = require('events').EventEmitter;    
var follow = require('follow');

Then we create and export an event emitter:

var config = module.exports = new EventEmitter();

Now we need to start following the CouchDB changes feed:

var source = process.env.CONFIG\_SOURCE\_URL || 'http://127.0.0.1:5984/config';    
var seq = 0;
feed = new follow.Feed({    
  db: source,  
  since: seq,  
  include\_docs: true  
});
feed.on('change', onConfigChange);
feed.follow();

This piece of code starts listening to changes on the base URL defined in the environment variable CONFIG_SOURCE_URL. If such a variable doesn’t exist, it defaults to using the local host (which is convenient during development).

We’re also telling the follow to start since the sequence number 0, which is the same as asking it to feed us the entire history of the config database.

Then we’re listening to the change event on the feed, effectively calling out onConfigChange function for each change on the config database.

Finally, we start following the feed by calling feed.follow().

After all this, we’re exporting a stop function that stops the feed:

config.stop = function() {    
  feed.stop();  
};

This function allows us to stop reading from the config, which can be useful when shutting down the process.

Now, the only remaining piece of code to analyse is our onConfigChange function that handles configuration changes from CouchDB:

function onConfigChange(change) {    
  var id = change.id;  
  var doc;
  if (change.deleted) {  
    delete config\[id\];  
  }  
  else {  
    doc = change.doc;  
    delete doc.\_id;  
    delete doc.\_rev;
    config\[id\] = doc;  
  }
  config.emit('change', id, doc);
  console.log('config\["%s"\] after change:', id, doc);  
}

Every time there is a change, this function gets invoked with a change document describing the change. We start by extracting the id of the changed document. If the change was a document deletion, we delete that key from our exported config object. If this is not a deletion, it’s an update or an insert, in which case we update the respective key on our exported config document.

After making the change, we emit a change event to allow our clients to be notified of changes.

I> By listening to this change event, a configuration client can, for instance, detect that it needs to change the hostname of a database server and connect to that server.

Let’s now watch this in action. First, start our module directly:

$ node config

You will almost immediately see the first change on our database:

config\["email.send"\] after change: { user: 'SOME.USER.ID',    
  token: 'A.SECRET.ACCESS.TOKEN',  
  defaultSender: 'info@example.com' }

You can now use the Couchdb Futon admin interface to change the configuration value:

Once you save the document (using the Save Document button), you will see that change in your Node process output:

config\["email.send"\] after change: { user: 'SOME.OTHER.USER.ID',    
  token: 'A.SECRET.ACCESS.TOKEN',  
  defaultSender: 'info@example.com' }

Client example

Here is an example of a client of our config module:

var config = require('./config');
function sendEmail(to, body, callback) {    
  var emailSendConfig = config\['email.send'\];  
  options = {  
    user:  emailSendConfig.user,  
    token: emailSendConfig.token,  
    from:  emailSendConfig.defaultSender,  
    to: to,  
    body: body  
  };
  // actually contact the external service to send that email
  sendEmailUsingThirdPartyProvider(options, callback);  
}

Next Article

This article completes the series about configuration patterns in Node.js.

You can find the previous post on this topic here:

In the next series I’ll start the analysis of some networking patterns. In particular, in the next article we’ll start by building a simple TCP service, which we’ll then make evolve throughout future articles.

This article was extracted from the Configuration Patterns, a book from the Node Patterns series.

Written by Pedro Teixeira — published for YLD.

You may also like:


Written by YLDFebruary 4th, 2016


Share this article