Thursday, 4 February 2016

Writing a pure javascript repository pattern

My javascripting needs work!

I suffer from javascriptus horribilis. I don't know why all my knowledge of object oriented programming and patterns disappear when I write javascript. It feels like I'm coding myself into a corner all the time. But I'm trying to improve. One step was to try and mix a bit of factory and repository thinking into my javascripts. Don't you just feel the weight of the Gang of Four-book sweeping in by now?


Basic train of thought

So the idea is this; think layers in javascript. That sounds like a very non-hipster thing I guess, stiff and overly complicated, but if we think 'components' or 'modules' instead it gets more hip by the minute. What I wanted to do was basically:
  • The calling component calls a repository, with a callback to trigger when the data returns.
  • A repository factory provides the available repositories to the calling component.
  • The repository exposes public methods for CRUD, provides data from some source and calls the callback provided from the calling component. Whether the source is static content, an external service or whatnot is not important, and the calling component doesn't have to know anything about that.
Code is available on Github. I like the Node-way of requiring modules, so I use Browserify to be able to write my code in commonjs-style.


A CRUD Repository

Let's start with creating a basic User Repository, userRepository.js.
var userRepository = function() {

    var get = function(id, callback) {
        callback();
    };

    var list = function(callback) {
        callback();
    };

    var save = function(user, callback) {
        callback();
    };

    return {
        get: get,
        list: list,
        save: save
    };
};

module.exports = userRepository();
The code executes when it is required (module.exports = userRepository()) and the methods get, list and save are exposed. Now we can require and call the repository from our main.js file:
var userRepo = require("./userRepository");
userRepo.get(1, getUser);

function getUser() {
    console.log("got the user");
}
This code requires the user repository and assigns it to the variable userRepo. Now we can call the get-method and send along the callback function that we want the user repository to execute when the data is fetched. For now, the callback just logs a message.


Implementing the CRUD-methods

Now, what the repo actually does can of course be whatever. Fetch data from an array in the repo, from external or internal services or from text files. I just fake a service by fetching the content of a json-file, users.json.
[
  { "id":1, "name":"User Number One" },
  { "id":2, "name":"User Number Two" },
  { "id":3, "name":"User Number Three" },
  { "id":4, "name":"User Number Four" }
]
To fetch these, I want to make an Ajax-call using a promise. Granted, promise is not a standard in for example IE, but there are nice polyfillers out there that will do the trick. The nice thing about promises is that you can chain methods together and the catch-clause catches errors in all of the then-clauses. In userRepository.js I extend my get-implementation to this:
var get = function(id, callback) {
    ajax.makeRequest('GET', 'users.json')
        .then(function (data) {
            var users = JSON.parse(data);
            var user = users.filter(function(user) {
                return user.id === id;
            });
            if (user.length > 0)
                user = user[0];
            callback(user);
        })
        .catch(function (err) {
            console.error('Ouch, there was an error!', 
            err.statusText);
        });
};
I make a request to users.json and when the promise is resolved and returns, I parse the data, filters the users by id and returns the correct user. Yeah, not the most efficient way to get a user, agreed, but it's there to show that there's a middle layer between the actual ajax-request and the component needing the data. :)

When the user is filtered and ready, the callback is executed, sending along the user. The ajax-component being used in the repo looks like this, but it's outside the scope here and really not important:
var ajax = function () {

    var createParams = function (params) {
        if (params && typeof params === 'object') {
            params = Object.keys(params).map(function (key) {
                return encodeURIComponent(key) + '=' 
                  + encodeURIComponent(params[key]);
            }).join('&');
        }
        return params;
    };

    var makeRequest = function (method, url, params) {
        return new Promise(function (resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.open(method, url);
            xhr.onload = function () {
                if (this.status >= 200 && this.status < 300) {
                    resolve(xhr.response);
                }
                else {
                    reject({
                        status: this.status,
                        statusText: xhr.statusText
                    })
                }
            };
            xhr.onerror = function () {
                reject({
                    status: this.status,
                    statusText: xhr.statusText
                });
            };
            if (params) {
                params = xhr.params = createParams(params);
            }
            xhr.send(params);
        });
    };

    return {
        makeRequest: makeRequest
    }
};

module.exports = ajax();


A repository factory, just because factories are cool

After writing all possible repositories, the code in main.js looks quite nice. Lots of different repositories being required and assigned to variables though.
var dom = require("./domManager");
var userRepo = require("./userRepository");
var catRepo = require("./catRepository");
var colourRepo = require("./colourRepository");

userRepo.list(listUsers);
catRepo.list(listCats);
colourRepo.list(listColours);
colourRepo.get("magenta", showColour);

function listUsers(users) {
  users.forEach(function(user) {
      dom(".userList").addListItem(user.name, user.id);
  });
}

function listCats(cats) {
  var catsWithImages = cats.filter(function(cat) {
      return "image" in cat;
  });

  catsWithImages.forEach(function(cat) {
      dom(".catList").addHtml(
       "<div><img src='"+cat.image+"'></div>"
      );
  });
}

function listColours(cols) {
  for (var col in cols) {
      dom(".colourList").addHtml(
       "<div><b style='color:"+cols[col]+"'>"+col+"</b></div>"
      );
  }
}

function showColour(hex) {
  dom(".bestColour").element.style.backgroundColor = hex;
}
The repository factory to the rescue! Easy-breezy repositoryFactory.js takes care of the plumming:
var repositoryFactory = function() {
    var repos = this;
    var repositories = [
      {name: "users", source: require("./userRepository")},
      {name: "cats", source: require("./catRepository")},
      {name: "colours", source: require("./colourRepository")}
    ];

    repositories.forEach(function(repo) {
       repos[repo.name] = repo.source;
    });
};

module.exports = new repositoryFactory();
The factory contains an array with all available repositories. When it is required, it loops through the repos in the array, requires all of them and assigns them to properties on 'this'. Since they execute when they are required, they're all exposing their public methods and are ready to be used. Neat and tidy. And now we can do this in main.js:
var repos = require("./repositoryFactory");

repos.users.list(listUsers);
repos.cats.list(listCats);
repos.colours.list(listColours);
repos.colours.get("magenta", showColour);

No comments:

Post a Comment