As our applications get larger and larger, it’s important to decouple the various functional areas. Decoupling them – or rather, coupling them correctly – allows much greater flexibility by letting us change how each functional works, without affecting the other areas.
As an example of functional areas, here’s a wireframe of something similar to what I’m currently working on:
The application shown here is a very simple item management app. On the left, you have a tree with a hierarchy of items. On the top right, you have a grid view to show the child items of the item that was selected in the tree. On the bottom right, you have an add/edit form.
I’ve only just begun to explore these patterns and architectures in the last few months, but I wanted to share my perspective and what I’m currently doing.
The use of the parenthesis around the function definition allows the function to be returned, ready to go (if you forget the parenthesis around the function, you’ll get a syntax error).The second pair of parenthesis immediately execute the returned function. The result can be assigned to a variable, which becomes a reference to the module’s public API (if anything was returned).
Organizing Your Backbone.js Applications With Modules – This article from Bocoup.com covers the basics of file organization and creating a way to easily reference your modules from other modules, using strings as names.
Organzing your application using Modules (Require.js) – This article from BackboneTutorials covers much of the same ground, but does it using the Require.js framework.
External / Shared Code
In spite of our desire to separate each of the functional areas of the application, there is a high likelihood that they will need to talk to each other and have access to some of the same code. You’ll also want to have access to some external libraries, such as jQuery, Underscore.js, Backbone.js, or any of a number of other libraries and tools that your code uses.
The same thing applies to your own modules and libraries. If you have a module defined somewhere else and you know it will be available before the module your currently working on is loaded, you can pass in a reference like this.
(You should also know that once you start heading down this path, you may want to think about using asynchronous module definitions: AMDs. Tools such as RequireJS make AMDs easier to deal with by handling the dependency management, module definition and other common tasks for you.)
To work around this problem, modules need to have some sort of initialization code in them. Module frameworks like RequireJS have this built in to them. In the app I’m writing, I’m not using RequireJS or any other module frameworks, so I wrote my own registration. It’s pretty simple when it comes down to it. Since every module I write for my app receives an app object, I attached a `addInitializer` function to it. This allows each module to add it’s own initializer code as a callback function, like this:
The application object keeps track of all the callbacks that were passed in to the `addInitializer` function. When the overall application is being initialized, a call to the app object’s `initialize` method will loop through each of the registered module initializers and call them. Each module gets to do it’s own thing and spin itself up.
Event Driven Architecture
Modules are great for organizing your code, but present a challenge when you realize that you need these modules to communicate with each other. Your code is now separated into different files, encapsulated in different modules, and generally unable to make direct calls in to your other modules and objects like you may be used to. Don’t worry, though. You’re only bumping in to the next steps of decoupling your applications correctly.
There are many different ways that you can solve this problem, of course. One of my favorite ways is the use of an event aggregator. I’ve blogged about the use of an event aggregator with Backbone already. If you need an introduction to the idea, check out that post and some of my many other Winforms / Application Controller posts. The benefit of an event aggregator in this case, is that is gives you a simple, decoupled way to facilitate communication between your modules.
To get started, you need to have a module or other object that is defined prior to any other modules being defined. This object needs to be passed in to each of the modules that you’re defining, so that these modules can have access to the event aggregator. In my app, I put the event aggregator directly on the top level application namespace, and then pass the namespace object into each of my modules:
Now each of my modules can bind to and trigger events from the event aggregator. This allows one module to send notification of something that happened, without having to know specifically which parts of the application are going to respond to that notification, or how.
One thing you need to think about, in advance, is what events you’re going to be triggering through the event aggregator. It’s a good idea to have thought through this at least a little bit, so that you don’t end up with a giant mess of similar and undocumented events. I made this mistake and paid for it when I couldn’t figure out which events needed to be fired, when. By taking the time to think through which events needed to be fired when, though, I was able to plan the communication between my modules, document the catalog of events, and clean up the number of events that were being used. Having this documented is important so that other developers (or you, a month from now) can see if there’s already an event that has the semantics they need, when writing new code.
To solve this problem, you need to use a packaging system. These systems will take all of the files that you specify, bundle them into a single file, optionally minify the whole thing and then provide that one file as a single resource for the browser. RequireJS has an associated optimizer to handle this. Rails 3.1 has the asset pipeline to do this. There’s the ruby gem “Jammit” which also does this for Ruby / Rails app (and Sinatra, etc). In .NET land, there’s several available including SquishIt. And there are still more in these and in other languages and environments.
If you don’t make use of one of these packaging tools, you’re going to cause problems for you users. Pick one, learn it, use it.
So Much More: Resources
- https://github.com/eric-brechemier/lb_js_scalableApp (Thanks to Aaron Mc Adam for the tip on the slides!)