Nate Davis Olds

Ideas. Presentations. Showcases.

Building Coreisma

| Comments

A while back, I watched Scalable JavaScript Application Architecture by Nicholas Zakas. It describes an architecture made up of isolated modules communicating through a central sandbox. These concepts resonated with me. So I decide to write this basic architecture as fun exercise. Turns out, it wasn’t difficult to implement.

The result is Coreisma.

Coreisma is a lightweight module manager for javascript. It provides a clear way to encapsulate javascript snippets into modules, facilitates communication between modules through event notifications, and exposes shared code in through extensions.

In this article, I’d like to share the steps I took to build it.

Core Philosophies

I believe that good software is written deliberately. Meaning the choices that a programmer chooses should come from a set of philosophies. For Coreisma, I these philosophies to:

  1. Create a simple architecture pattern; not another framework
  2. Not tie myself to a particular framework (ie. jQuery, Prototype, YUI)
  3. Use TDD to ensure test coverage.

The Small Steps

Coreisma starts, as all very abstract cores should, very small. I simply provided a means to extend the object itself. Pass a json object or more powerfully a function that receives Coreisma itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
var Coreisma = {
  extend: function(extension) {
    var extensionMethods = (typeof extension === 'function') ? extension(this) : extension;

    for (var property in extensionMethods) {
      if (extensionMethods.hasOwnProperty(property)) {
        Coreisma[property] = extensionMethods[property];
      }
    }

    return extensionMethods;
  }
};

Then, using this extend function, I exposed a more developer-friendly way to add an extension. I boringly called this function addExtension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Coreisma.extend(function(core) {
  var extensions = [];

  var addExtension = function(extensionFunction) {
    if (arguments.length > 1 && typeof arguments[0] !== 'function') { extensionFunction = arguments[1]; }

    var extension = (typeof extensionFunction === 'function') ? extensionFunction(core, core.hub) : extensionFunction;

    core.extend(extension);

    extensions.push(extension);

    return extension;
  };

  return {
    addExtension: addExtension
  };
});

I wanted a way to name extensions. It isn’t retained or used in any way but it is a nice way to briefly describe what the extension does. Maybe one day this will allow for some simple self documentation.

Likewise, addExtension stores all extensions in a, you guessed it, “extensions” private variable.

addExtension passes core and a hub to an extension. At first hub is undefined, but coreisma wouldn’t be useful without it, so I figure it is a safe assumption that some “hub” will be there.

I probably should have broken the next extension up into three separate extensions: addHub, addModule and getModule, and finally start and stop of Coreisma. I believe that the root of this was because I was TDDing. I could not seem to think of a way to test addHub without starting Coreisma. Seems like a good candidate for a refactor, but for now here is the “Hub” portion of Coreisma.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Coreisma.addExtension("Hub", function(core) {
  var addHub = function(hubFunction) {
    core.hub = (typeof hubFunction === 'function') ? hubFunction(core) : hubFunction;
  };

  var addModule = function(moduleName, moduleFunction) {
    if (core.hub) { core.hub.register(moduleName, moduleFunction); }
  };

  var getModule = function(moduleName) {
    return (core.hub) ? core.hub.find(moduleName) : {};
  };

  var start = function() {
    core.hub.broadcast('startup.modules', core.hub);
  };

  var stop = function() {
    core.hub.broadcast('shutdown.modules', core.hub);
  };

  return {
    addHub: addHub,
    addModule: addModule,
    getModule: getModule,
    hub: {},
    start: start,
    stop: stop
  };
});

Despite the name “Hub” of the previous extension, we haven’t defined a working hub yet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Coreisma.addHub(function(core) {
  var modules = {},
      callBacks = {};

  var broadcast = function(eventName, data) {
    var eventCallBacks = callBacks[eventName];

    for (var i in eventCallBacks) {
      if (eventCallBacks.hasOwnProperty(i)) {
        eventCallBacks[i](data);
      }
    }
  };

  var listen = function(eventName, module) {
    if (!callBacks[eventName]) { callBacks[eventName] = []; }

    callBacks[eventName].push(module);
  };

  var register = function(moduleName, moduleDefinition) {
    var module = (typeof moduleDefinition === 'function') ? moduleDefinition(core, core.hub) : moduleDefinition;

    modules[moduleName] = module;

    if (typeof module.init === 'function') { listen('startup.modules', module.init); }
    if (typeof module.shutdown === 'function') { listen('shutdown.modules', module.shutdown); }
  };

  var find = function(moduleName) {
    return modules[moduleName];
  };

  return {
    listen: listen,
    broadcast: broadcast,
    register: register,
    find: find
  };
});

The Hub exposes four functions: register, find, listen, and broadcast. They do what you expect them to do.

  • register makes a module known. It passes the core and hub to the module to work with and automatically sets up a modules init and shutdown functions

  • find retrieves a module by name.

  • listen setups up a callback when a certain event happens.

  • broadcast declares that a certain event has happened to all listeners.

Wrapping up

A scalable javascript architecture brings stability and standardization to any project. Coreisma is my attempt to implement the ideas from Nicholas Zakas. I am proud of how straight-forward Coreisma turned out.

At Benefit Data Trust we have be been using Coreisma in production as a trial for months now. As we start to migrate all of our javascript to use coreisma, I suspect best practices and patterns will emerge. We will be able to distinguish what should be an extension. Or standardize the format for data passed between listener and broadcasts.

There is room to grow with Coreisma but I think this spunky architecture has a bunch of potential.

Comments