user1098965
user1098965

Reputation:

WebSockets plus RESTful interface, how to write DRY code in Node.js?

My Node.js application provides both WebSockets and RESTful interface. I wrote a little replacement for Backbone.synch to use with Socket.IO as transport.

DRY problem: callbacks executed upon client event contains almost the same logic as callbacks for RESTul paths. An example mapping between events and data emitted by the client, and the corresponding action:

+----------------+---------------------------------+--------------------+
| event emitted  | data emitted                    | RESTful URL        |
+----------------+---------------------------------+--------------------+
|     read:users | empty string                    |  GET /users        |
|     read:users | id of the model                 |  GET /users/:id    |
|   create:users | full model as JSON              |  POST /users       |
|  destroy:users | id of the model                 |  DELETE /users/:id |
|   update:users | full model as JSON (with id)    |  PUT /users/:id    |
|    patch:users | partial model as JSON (with id) |  PUT /users/:id    |
+----------------+---------------------------------+--------------------+

Example (99% of duplicated logic/code):

var UserModel = require('./models/user'); // Mongoose model

// Express path
app.get('/users/:id?', function (req, res)) {
    var query = !id ? {} : { _id: id };

    UserModel.find(query, function (err, doc) {
        return err ? res.send(404, null) : res.send(200, doc);
    });
};

// SocketIO listening to the read:users event
socket.on('read:users', function(id, cb) {
    var query = !id ? {} : { _id: id }

    UserModel.find(query, function (err, doc) {
        return err ? cb(err.message, null) : cb(null, doc);
    });
});

Because I'm playing with Node.JS and event programming (and JavaScript) for a few days, I'm looking for a good advice of how design a "controller", like a general purpose object that can handle the duplicated code easily. Thanks.

Upvotes: 4

Views: 598

Answers (1)

Default
Default

Reputation: 16518

If you really want to mix the logic of those two functions, then the way that your client side code passes the arguments to your socket.on callback function will have to be changed. You can approach it with the current set up like this:

var veryGenericCallback = function(p1, p2) {
   // Note: Not sure what to name the arguments because they are wildly different
   // in your two different cases.

   var query = typeof p1 === "object" : {} : { _id: p1 };

   UserModel.find(query, function (err, doc) {
     var result;

     if (typeof p2 === "function") {
        return err ? p2(err.message, null) : p2(null, doc);
     } else {
        return err ? p2.send(404, null) : p2.send(200, dox);
     }

   });

}

But as you can see, at some point you will still have to repeat the logic that you are trying to avoid. However, if your socket client code that emits the event passed in an object that had an "id" property on the first argument, and passed an object with a send function for your second argument, you could reduce it to this:

var veryGenericCallback = function(info, action) {

   var query = info.id ? { _id: info.id } : {};

   UserModel.find(query, function (err, doc) {

     return err ? action.send(404, null) : action.send(200, doc);

   });

}

However in this case you would need to change the code on your server side to handle whatever it is your pseudo-send method does (and you would need to define that in the first place somewhere the client has access to it). This also severely restricts what you can do in your callbacks, as any function you want to call on an actual Request/Response object will have to be mimicked in your socket client code. This muddles the readability and extensiblity of your code in my opinion, but it certainly can be achieved.

Upvotes: 1

Related Questions